c# Inversion of Control – IoC ¿Cuándo o cuándo no debemos invertir el control de código?

Martin Fowler - Inversión de control
Martin Fowler – Inversión de control

Muchas veces creemos que al decidir aplicar la inversión de control se tiene que aplicar a todas las clases e interfaces que formen el sistema, con esto me refiero a que se tiende a especificar que clase se tiene que devolver por cada interfaz. Reflexionemos un momento y respondamos las siguientes preguntas: ¿Qué tan común será que reemplaces una clase por otra en tu código? ¿Existe alguna necesidad o requerimiento que señale que no se debe depender de algo? Con esto no quiero decir que se deba desarrollar código acoplado al 100%, NO, al punto que quiero llegar es poder identificar cuándo o cuándo no se debe invertir el control de código y saber a qué partes del código debo aplicar la inversión de control.

Para descargar el código haz clic acá. En este ejemplo también se ve la configuración de un servicio WCF para que soporte la inversión de control usando el contenedor SimpleInjector.

1. Escenario

Para esta parte tenemos los siguientes requerimientos:

  1. Req1: Permitir a los usuarios validar sus credenciales contra el Active Directory.
  2. Req2: Todos las llamadas deben guardarse en un archivo de texto (parámetro enviado  y fecha).
  3. Req3: Permitir a los usuarios subir y descargar archivos en un repositorio genérico. Para esta versión se deberá trabajar con un servidor de archivos, pero se debe contar con la facilidad de poder cambiarlo más adelante.
  4. Req4: Mantener bajo el acoplamiento.

2. Identificar los tipos de dependencias

Analicemos cada uno de los requerimientos para identificar aquellos que si necesiten ser diseñados pensando en el desacoplamiento desde el inicio.

  • El requerimiento 1 menciona que se deben validar las credenciales contra el Active Directory, pero no menciona nada que nos ayude a decidir si debe estar o no desacoplado. Es una dependencia fija.
  • Para el requerimiento 2 ocurre lo mismo, se menciona que todos los mensajes deben ser registrados en un archivo de texto. Es una dependencia fija.
  • Creo que el requerimiento 3 es muy obvio, se solicita que no se dependa del repositorio donde se guarden los archivos sino que se trabaje de una forma tal que permita hacer el cambio más adelante. Es una dependencia variable.

3. Diseño de la solución

En este caso como se nos pide cuidar el acoplamiento, sobre todo en la parte de subir y descargar archivos, vamos a implementar un diseño desacoplado para esta solución.

3.1 Modelo
public class Credencial
{
    public string Usuario { get; set; }
    public string Contrasena { get; set; }
}

public class Multimedia
{
    public string Identificador { get; set; }
    public byte[] Contenido { get; set; }
    public Multimedia()
    {
        Identificador = Guid.NewGuid().ToString();
    }
}
3.2 Contratos
public interface ILog
{
    void RegistrarMensaje(string mensaje);
}

public interface IRepositorioCredencial
{
    bool AutenticarUsuario(Credencial credencial);
}

public interface IRepositorioMultimedia
{
    void Almacenar(Multimedia multimedia);
    Multimedia RecuperarPorIdentificador(string identificador);
}
3.3 Servicios
public interface IServicioAutenticacion
{
    bool AutenticarUsuario(Credencial credencial);
}

public interface IServicioGestorMultimedia
{
    void Almacenar(Multimedia multimedia);
    Multimedia RecuperarPorIdentificador(string identificador);
}
3.4 Implementación del servicio de autenticación
public class ServicioAutenticacion : IServicioAutenticacion
{
    private readonly ILog _log;
    private readonly IRepositorioCredencial _repositorioCredencial;

    public ServicioAutenticacion(ILog log, IRepositorioCredencial repositorioCredencial)
    {
        _log = log;
        _repositorioCredencial = repositorioCredencial;
    }

    public bool AutenticarUsuario(Credencial credencial)
    {
        _log.RegistrarMensaje(string.Format("Autenticacion usuario: {0} - Fecha: {1}",credencial.Usuario, DateTime.Now));

        return _repositorioCredencial.AutenticarUsuario(credencial);
    }
}
3.5 Implementación del servicio gestor de multimedia
public class ServicioGestorMultimedia : IServicioGestorMultimedia
{

    private readonly ILog _log;
    private readonly IRepositorioMultimedia _repositorioMultimedia;

    public ServicioGestorMultimedia(ILog log, IRepositorioMultimedia repositorioMultimedia)
    {
        _log = log;
        _repositorioMultimedia = repositorioMultimedia;
    }

    public void Almacenar(Multimedia multimedia)
    {
        _log.RegistrarMensaje(string.Format("Almacenar archivo: {0} - Fecha: {1}", multimedia.Identificador, DateTime.Now));

        _repositorioMultimedia.Almacenar(multimedia);
    }

    public Multimedia RecuperarPorIdentificador(string identificador)
    {
        _log.RegistrarMensaje(string.Format("Recuperar archivo: {0} - Fecha: {1}", identificador, DateTime.Now));

        return _repositorioMultimedia.RecuperarPorIdentificador(identificador);
    }
}
3.6 Servicio Wcf de autenticación
[ServiceContract]
public interface IServicioWebAutenticacion
{
    [OperationContract]
    void Autenticar(Credencial credencial);
}

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerCall)]
public class ServicioWebAutenticacion : IServicioWebAutenticacion
{
    private readonly IServicioAutenticacion _servicioAutenticacion;

    public ServicioWebAutenticacion(IServicioAutenticacion servicioAutenticacion)
    {
        _servicioAutenticacion = servicioAutenticacion;
    }

    public void Autenticar(Credencial credencial)
    {
        _servicioAutenticacion.AutenticarUsuario(credencial);
    }
}
3.7 Servicio Wcf gestor de multimedia
[ServiceContract]
public interface IServicioWebGestorMultimedia
{
    [OperationContract]
    void Almacenar(Multimedia multimedia);

    [OperationContract]
    Multimedia RecuperarPorIdentificador(string identificador);
}

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.PerCall)]
public class ServicioWebGestorMultimedia : IServicioWebGestorMultimedia
{
    private readonly IServicioGestorMultimedia _servicioGestorMultimedia;

    public ServicioWebGestorMultimedia(IServicioGestorMultimedia servicioGestorMultimedia)
    {
        _servicioGestorMultimedia = servicioGestorMultimedia;
    }

    public void Almacenar(Multimedia multimedia)
    {
        _servicioGestorMultimedia.Almacenar(multimedia);
    }

    public Multimedia RecuperarPorIdentificador(string identificador)
    {
        return _servicioGestorMultimedia.RecuperarPorIdentificador(identificador);
    }
}

4. Aplicando la inversión de control

Acá empieza lo bueno, en esta parte veremos que no es necesario que se invierta el 100% del código de una solución, así sea una dependencia fija o variable. Solo se debe invertir el control de la instancia de la clase que es usada por el servicio directamente o de aquellas que se usan en más de un lado como es el caso de la clase LogArchivoTexto.

public class Bootstrapper
{
    private static Container _contenedor;
    
    //Codigo

    private static void RegistrarWcfDependencias()
    {
        string rutaLog = string.Empty;
        string rutaFileServer = string.Empty;
        string rutaActiveDirectory = string.Empty;

        _contenedor.RegisterPerWcfOperation(
           ()=>(ILog)new LogArchivoTexto(rutaLog));

        _contenedor.RegisterPerWcfOperation(() =>
            (IServicioAutenticacion)new ServicioAutenticacion(
                _contenedor.GetInstance<ILog>(),
                new RepositorioCredencialActiveDirectory(rutaActiveDirectory)), true);
            
        _contenedor.RegisterPerWcfOperation(() =>
            (IServicioGestorMultimedia)new ServicioGestorMultimedia(
                _contenedor.GetInstance<ILog>(),
                new RepositorioMultimediaFileServer(rutaFileServer)), true);
    }
}

De repente se preguntaran:

  • ¿Por qué importa si una dependencia es fija o variable?
    • Respuesta: Para saber cuáles son las partes del sistema que sí o sí deben estar desacopladas, con la finalidad de que puedan ser reemplazadas sin problemas.
  • ¿Por qué a la final para los dos tipos de dependencias se usó la inversión de control?
    • Respuesta: Para mantener centralizada la definición de las instancias y evitar el tener que revisar todo el código para saber dónde está siendo usada una clase, pero es importante resaltar que la inversión de control no fue usado para todas las clases, solo fue usado en las clases que son referenciadas directamente por el servicio.

Con esto logramos:

  • El sistema se mantiene igual de desacoplado, sea una dependencia fija o no, recuerden que siempre se debe pensar en el mantenimiento a futuro del sistema.
  • El cambio de cualquier implementación se hace en un solo punto y se refleja a todos los que la usan, Root of composition.
  • La cantidad de relaciones entre interfaces e instancias, que se mantienen en memoria, permanecen bajos ya que solo se hace referencia a pocas clases.

Para descargar el código haz clic acá. En este ejemplo también se ve la configuración de un servicio WCF para que soporte la inversión de control usando el contenedor SimpleInjector.

Conclusión

Invertir el código no es malo, pero hay que hacerlo con cuidado e identificar adecuadamente sobre que clases se debe aplicar. Lo recomendado es aplicarlo en clases que son usadas directamente en los límites del sistema, como un servicio web o un controlador. Al momento de crear las instancias de estas clases se les debe pasar todas las dependencias que necesite el objeto, este punto se convierte en la raíz de composición (Root of composition). Al aplicar esta recomendación se disminuye la cantidad de relaciones, interfaz -> clase, que existen en memoria.

Referencias:
Metal Tip:

Este artículo lo escribí escuchando la canción A Tale that wasn´t right de la banda Helloween, les comparto el enlace.

Anuncios

4 comentarios en “c# Inversion of Control – IoC ¿Cuándo o cuándo no debemos invertir el control de código?

  1. Interesante, concuerdo contigo de que se debe analizar primero los requerimientos y no utlizar la inversion of control solo porque esta de moda o porque todo el mundo dice que se debe de hacer, muchas veces hay que ver si realmente el alcance de la aplicación en un futuro puede cambiar y esta técnica nos va a aportar la faclidad de solo cambiar nuestra clase concreta sin tener que afectar mas partes del sistema.

    Le gusta a 2 personas

    1. Hola Franklin, lo recomendado es no hacer las cosas solo porque nos gusta sino porque sabemos que es la decisión correcta, previa prueba de concepto :).

      Me gusta

  2. Muy bueno, se puede pensar como cuando hacemos una casa, hay cosas que van a ser fijas y casi nunca se van a querer cambiar, como la chimenea por ejemplo, otras cosas si, como los enchufes a las lamparas, se pueden necesitar desenchufar, enchufar otro aparato, etc.

    Le gusta a 1 persona

    1. Hola Antonino, muy buen ejemplo el del enchufe. El enchufe tiene una interfaz genérica para que cualquier dispositivo, que cumpla con esa interfaz, se pueda conectar a el. Lo cual no aplica a las demás partes de la casa.

      Me gusta

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s