martes, 19 de mayo de 2015

Dale la vuelta antes de decidir.

No, en este post, no voy a recomendar que se cuelgue uno del pie para que llegue más sangre a la cabeza, se oxigene mejor el cerebro y se gane un par de puntos de C.I.

El título de este post proviene de ver como, muchas veces, las decisiones en cuanto al diseño y desarrollo de un sistema de información se hacen al revés. Por esto, recomiendo que siempre le deis la vuelta a la solución que se os a ocurrido y la analicéis en profundidad para determinar si es una mejor solución.

Los resultados de estas decisiones desastrosas es lo que se llama "Gran bola de lodo" y estoy seguro que muchos de vosotros lo habéis sufrido en algún modo o lo estáis sufriendo ahora mismo.


Existen 2 campos importantes de decisiones en las que siempre se debería hacer esto de mirar las cosas desde el otro extremo:

Solución técnica y solución de dominio


El infame borrado lógico es un clásico en este caso. Se esta proponiendo un solución técnica a un problema que necesita una solución de dominio. Ver mi anterior post sobre los borrados lógicos para comprender en profundidad a lo que me refiero.

Aparte de que los borrados lógicos son especialmente dolorosos, tomar una solución técnica cuando toca una solución de dominio se convierte en un problema por la intrusión en el dominio y perdida de contexto que resulta. Modificaremos las reglas del dominio con comportamientos poco intuitivos, aumentará exponencialmente la complejidad ciclomática, empezaremos a tener problemas para segregar responsabilidades y crear estructuras de clases, herencia e interfaces bien definidos, desacoplados y con un contexto y propósito claro; incluso nos empezará a costar ponerle nombres autoexplicativos a las clases, métodos y variables por culpa de esa falta de contexto.

El caso contrario también tiene su miga. Si le pegan un vistazo a mi post anterior sobre el estancamiento de datos se puede llegar a pensar que la solución al cambio de dirección de facturación pasa por agregar un campo de fecha y/u hora a la solicitud y simplemente no aplicar el cambio si la solicitud tiene fecha anterior a la persistida. En un principio esto es totalmente aceptable; el problema es que estamos inventándonos reglas de dominio que no existen en el proceso formalizado que siguen los usuarios de la aplicación. Si el proceso de negocio que siguen los trabajadores no contempla de forma oficial un registro organizado de solicitudes donde se las identifica de forma única y con una fecha y hora estandarizada; establecer una solución de dominio sólo causara caos.

Como la solución no pertenece al proceso de negocio "físico" no tendremos un lenguaje ubicuo con el que clasificarlo y acabaremos con un un feo "Fecha de inserción del campo" totalmente descontextualizado y sin sentido aparente para los usuarios.

Como los usuarios no tienen un registro oficial, empezarán a inventarse las fechas que tienen que insertarse obligatóriamente en el sistema; pondrán la fecha en que les llegó el papel, la fecha en la que les pusieron la tarea en la cola de cosas que hacer, siempre la fecha de hoy..., etc. Cada uno se inventará su método, lo único que importa es poner algo para que este sistema tan quisquilloso e incomodo que les han desarrollado les deje realizar el cambio.

Los usuarios solicitarán que se pueda cambiar a su conveniencia la fecha persistida cuando encuentren un cambio de un dato que ellos saben seguro que tiene que poderse cambiar pero que el sistema no les deja porque la fecha persistida es posterior.
Como esos cambios son excepciones, los usuarios pedirán un rol especifico para ello que sólo tenga el supervisor del departamento.
Como esas excepciones son delicadas, los usuarios pedirán un sistema de notificaciones para el director general cuando se realice alguna tarea excepcional de este tipo por parte del supervisor.

Y así, vamos añadiendo una plétora de bizarras y descontextualizadas características al sistema que no tienen nada que ver con los procesos de negocio que existen en la organización del cliente.

Diseño y arquitectura


En este caso, es el diseño de dependencias en un grafo de clases lo que se puede llegar a hacer del revés. Para ilustrarlo voy a poner un sencillo caso que es muy bueno como ejercicio para identificar programadores, analistas y arquitectos despiertos.

Desarrollar una sencilla aplicación de consola que acepte los siguientes comandos:

  • insertar cliente [nombreCliente]
  • insertar proveedor [nombreProveedor]
  • insertar producto [nombreProducto]
  • borrar cliente [nombreCliente]
  • borrar proveedor [nombreProveedor]
  • borrar producto [nombreProducto]
  • buscar cliente [nombreCliente]
  • buscar proveedor [nombreProveedor]
  • buscar producto [nombreProducto]

La persistencia se realizará en memoria y se desea que se implemente un patrón de diseño de comandos sencillo que se adapte a las necesidades y se apliquen todas las buenas prácticas organizativas y arquitectónicas se se consideren oportunas sin tener en cuenta que, en este caso tan sencillo, sea sobrediseñar.

Aparte de todo lo que la implementación de este ejercicio puede contar del entrevistado, vamos al caso que nos ocupa.

Si uno no se para a pensar y hace las cosas a salto de mata, los comandos de la aplicación terminarían de esta guisa:

class InsertCommand : ICommand<byte> {

EnumEntidad ent;
string nombre;

IClienteService servicioClientes = new ClienteServices;
IProveedoresService servicioProveedores = new ProveedorServices;
IProductoService servicioProductos = new ProductoServices;

InsertCommand(EnumEntidad entidad, string nombreEntidad){
    ent=entidad;
    nombre = nombreEntidad;
}

byte execute() : ICommand.execute {
    
    switch(ent){
        case Cliente:
            IClienteService.Insertar(nombre);
            return 0;
        case Proveedor:
            IProveedoresService.Insertar(nombre);
            return 0;
        case Producto:
            IProductoService.Insertar(nombre);
            return 0;
    } 
  }

ICommand Undo() : ICommand.Undo{
    delCmd = new DeleteCommand(ent, nombreEntidad);
    delCmd.execute();// o retornarlo simplemente y execute lo hace el coordinador de esta clase
    return delCmd; //para que el sistema guarde el ultimo comando ejecutado para volver a hacer Undo si el usuario lo solicita
}

}//end class

Y así con el resto de los comandos BuscarCommand y BorrarCommand.

Uno de los problemas que tenemos aquí es mantener dependencias de todos los servicios en cada comando. El día que cambie una interfaz de algún servicio hay que tocar todos los comandos. Por otra parte, estamos sacando los comandos del contexto. Tenemos un módulo de clientes, uno de proveedores y otro de productos pero no tenemos sus comandos asociados al mismo contexto. Estamos creando una intrusión entre las fronteras que hemos diseñado. La maraña de depencias es muy alta; teniendo referencias a todos los servicios del dominio y a su comando contrario para realizar la operación de deshacer.

La solución, como ya se puede uno imaginar a esta altura del post, es invertir el diseño y crear comandos que estén asociados a los contextos con los que van a trabajar.

class ClienteCommand : ICommand<List<Clientes>> {

EnumOperacion op;
string nombre;

IClienteService servicioClientes = new ClienteServices;

ClienteCommand(EnumOperacion operacion, string nombreEntidad){}
    op=operacion;
    nombre = nombreEntidad;
}

List<Clientes> execute() : ICommand.execute{
    
    switch(op){
        case Insertar:
            IClienteService.Insertar(nombre);
            return;
        case Borrar:
            IClienteService.Borrar(nombre);
            return;
        case Buscar:
            return IClienteService.Buscar(nombre);
    } 
}

void Undo() : ICommand.Undo{

    switch(op){
        case Insertar:
            op = Borrar;
            this.execute();
        case Borrar:
            op=Insertar;
            this.execute();
    }   
}

}//end class

Con esto tenemos un comando dentro de las fronteras del contexto de los clientes. Sólo realiza operaciones con los clientes por lo que eliminamos la maraña de dependencias del grafo de objetos. El día que la interfaz del servicio de clientes cambie sabremos que sólo debemos tocar el comando de operaciones con los clientes. La característica de deshacer no implica la creación ni el retorno de otro comando y se podría invocar Undo al mismo comando varias veces sin implicar ningún control externo.

No hay comentarios:

Publicar un comentario