Validaciones de argumentos, y entidades de negocio: FluentValidation y varios

334 views
Skip to first unread message

Kiquenet

unread,
Feb 13, 2014, 10:44:43 AM2/13/14
to altnet-...@googlegroups.com
Hola,

para aclarar conceptos y recopilar las distintas experiencias que puedan aportar expongo este tema.

Se pretende ver en el estado del arte actual de 2014 cómo realizar validaciones, de la forma más simple y fácil posible.

En concreto, podemos plantear dos:

1. Validaciones de argumentos

     Podemos pensar en un "ArgumentHelper" que realice este tipo de validaciones. 
     Se me ocurre los "Contracts" en su momento. No sé si existe otro tipo en este momento.

2. Validaciones de entidades de negocio.

    Por lo poco que sé, se puede plantear varios:

         Contracts
         Fluent Validation (está en Nuget y además en codeplex)
         Enterprise Library Validation
         Otras... ¿?

También añadir que pensemos en una aplicación cliente WPF o Windows Forms.

Ahora supongamos también otros casos: ASP.NET MVC y Windows Phone.

ASP.NET MVC  podemos aplicar Fluent Validation y algo que llaman DataAnotations.


En fin, hay una variedad de librerías a utilizar. Imagino que habrá más opciones más.


Algunas recomendaciones, sugerencias, experiencias para este tema de validaciones?

Desconozco totalmente este tema, lo poco que vi de Fluent Validation me gustó por lo simple y fácil que parece. (tiene sus ValidationResults, etc..)

Saludos y muchas gracias.

Kiquenet

unread,
Feb 14, 2014, 11:39:14 AM2/14/14
to altnet-...@googlegroups.com
Por ampliar, algunas referencias de las tantas que pueden encontrarse,


Algo genérico en el tema de "precondiciones", validaciones

http://stackoverflow.com/questions/3393179/any-net-fluent-argument-checking-libraries-out-there

 https://conditions.codeplex.com/ 


 

FluentValidation es bien reconocido en la comunidad

http://stackoverflow.com/questions/10915283/fluent-validation-or-entlib-validation-application-block-for-wcf-services

 


Ent.Library VAlidation App Block alguno le da mucho valor, DataAnotations para MVC hay que investigar

http://damienbod.wordpress.com/2013/08/01/validation-in-mvc-enterprise-library-fluent-validation-data-annotations-foolproof/

 


Contracts no sé si ya quedó atrás..

http://stackoverflow.com/questions/5217762/checking-preconditions-in-net


 

Luego hay otras tantas más…

http://blog.longle.net/2013/06/03/building-an-extensible-fluent-validation-framework-using-generic-funcs-and-wiring-it-up-to-mvc-4/

https://code.google.com/p/kosher/wiki/GettingStarted


 

En Nuget podemos encontrar un montón de librerías para validación, lo cual nos hace preguntarnos, cuál será la opción más adecuada 

http://nugetmusthaves.com/Tag/Validation

 

http://www.nuget.org/packages?q=validation

 

Yendo un paso más, si consideramos las validaciones de entidades de negocio como "Rules" (Reglas de negocio) ya un paso más es considerar en ciertas situaciones (con mucho más complejidad sin duda) una "state machine" y más allá un Workflow simple.


https://rulesengine.codeplex.com/

https://workflowengine.codeplex.com/


En definitiva, una variedad enorme a elegir.

Hasta ahora, por simplicidad FluentValidation me va gustando, todo dicho desde desconocimiento.





 

 

Carlos Peix

unread,
Feb 14, 2014, 3:38:53 PM2/14/14
to altnet-hispano
Hola Kiquenet,

En mi opinion, siempre es validacion de argumentos (mas complejos o menos complejos), nunca de entidades. Explico porqué.

Que es lo que validas? una determinada intención del usuario en alguna UI, seguramente. Yo creo que conviene reificar esa acción e incorporar validacion a la acción. Puede hacerse facilmente en el enfoque MVVM, sobre los view models.

Por ejemplo, en una pantalla de cambio de direccion de un cliente: Podemos tomar los datos, setearlos en la entidad cliente y luego validar la entidad completa o, mucho mejor, tomar los datos en un objeto CambioDeDireccionDeCliente y validadar si esos datos son correctos para luego hacer el cambio.

Si los datos fuesen correctos, entonces no habria problemas en aplicarlos a la entidad sin mas miramientos.

En este contexto es que me parece que siempre se trata de validar argumentos (una direccion postal, en este caso).

Luego, podrias aplicar el framework que fuese necesario para validar este objeto, desde los atributos de .NET hasta un metodo especial o frameworks de terceros.

Te parece razonable?

----------------------------------
Carlos Peix


--
Has recibido este mensaje porque estás suscrito al grupo "AltNet-Hispano" de Grupos de Google.
Para anular la suscripción a este grupo y dejar de recibir sus correos electrónicos, envía un correo electrónico a altnet-hispan...@googlegroups.com.
Para publicar una entrada en este grupo, envía un correo electrónico a altnet-...@googlegroups.com.
Visita este grupo en http://groups.google.com/group/altnet-hispano.
Para obtener más opciones, visita https://groups.google.com/groups/opt_out.

Uzi Mamani Fernández

unread,
Feb 16, 2014, 2:49:00 PM2/16/14
to altnet-...@googlegroups.com
Una que estoy recien usando es CodeGuard

https://github.com/3komma14/Guard

estamos probando en algunas soluciones del actual proyecto en el que estoy, es super sencilla

saludos



--
Has recibido este mensaje porque estás suscrito al grupo "AltNet-Hispano" de Grupos de Google.
Para anular la suscripción a este grupo y dejar de recibir sus correos electrónicos, envía un correo electrónico a altnet-hispan...@googlegroups.com.
Para publicar una entrada en este grupo, envía un correo electrónico a altnet-...@googlegroups.com.
Visita este grupo en http://groups.google.com/group/altnet-hispano.
Para obtener más opciones, visita https://groups.google.com/groups/opt_out.



--
Saludos

uZi

About me

Kiquenet

unread,
Feb 25, 2014, 3:34:16 PM2/25/14
to altnet-...@googlegroups.com
 
Carlos , muy interesante. Totalmente razonable. 

Comentando con el responsable, en qué situaciones aplicar "Reglas de negocio" en las que están implicadas entidades, sea un Windows Workflow, máquina de estados, u otra implementación básica?

Entiendo que la filosofía es más allá de la validación pura de argumentos (el caso de la dirección que comentabas), sino que trasciende a otro nivel conceptual.

 

Carlos Peix

unread,
Feb 25, 2014, 9:22:50 PM2/25/14
to altnet-hispano
Hola Kikenet,

En mi opinion, la logica de negocio esta dentro de las entidades y no fuera. Para mas referencias buscar Domain Driven Design.

----------------------------------
Carlos Peix


--

Walter Poch

unread,
Feb 26, 2014, 9:29:01 AM2/26/14
to altnet-...@googlegroups.com
Hola compañeros,

Yo por ejemplo valido mis "reglas de negocio" en las entidades cuando se puede, y sino en mis servicios de dominio (casos más complejos).

Por ejemplo en un sistema de asignación de trabajos tengo un método así:

        public Asignacion AgregarAsignacion(Usuario usuario, bool esResponsable = false, string comentario = null)
        {
            if (usuario == null) throw new ArgumentNullException("usuario");

            if (esResponsable && SubAsignaciones.Any(x => x.EsResponsable))
                throw new ValidationException("Ya existe una Asignación con un usuario marcado como Responsable.");

            if (Estado != EstadoAsignacion.Pendiente && Estado != EstadoAsignacion.Iniciado)
                throw new ValidationException("No se puede asignar personas a una Asignación que no esté en Pendiente o Inciado.");

            if(SubAsignaciones.Any(x=>x.UsuarioId == usuario.Id))
                throw new ValidationException(string.Format("El usuario '{0}' ya se encuentra asignado.", usuario.Nombre));

            // Si está pendiente la iniciamos automáticamente.
            if (Estado == EstadoAsignacion.Pendiente) Iniciar(Usuario, "Iniciada automáticamente al asignar un usuario.");

            SubAsignaciones.Add(new Asignacion(Requerimiento, Usuario, usuario, this, Nivel + Id + ".", esResponsable, comentario));

            return this;
        }

Más allá del tipo de Exception, lo he usado así y me siento bastante conforme, ese es un método de mi clase Asignación que es una entidad de mi dominio.

Saludos, 

--
Saludos,

Walter G. Poch
--------------------------------------------
Cell: +54 (9 341) 6836871
walte...@gmail.com

Carlos Peix

unread,
Feb 26, 2014, 8:43:49 PM2/26/14
to altnet-hispano
Hay varias perlas (cosas buenas) en ese codigo Wlater...

Por un lado, el metodo AgregarAsignacion. Es una tecnica que me gusta mucho. Cuando una objeto tiene una coleccion de otros objetos, los agrega la entidad padre validando los elementos y creanto la entidad en una coleccion. Si esta coleccion es privada, mejor o, al menos, que no se pueda agregar desde afuera (por ejemplo, publicando IEnumerable).

Lo unico que me llama la atencion es que devuelvas "this" en lugar de la instancia creada y agregada, como suelo hacer pero entiendo que puede ser para que te quede una interfaz mas "fluent" y puedas seguir llamando a metodos de la clase padre.

A esto me refiero con que la logica esta dentro de las entidades.

Saludos!

----------------------------------
Carlos Peix

Edgar Ramos

unread,
Feb 26, 2014, 9:16:14 PM2/26/14
to altnet-...@googlegroups.com
Estimados Carlos y Walter, que pasa si mi entidad de negocio tiene que validarse contra una base de datos, digamos por ejemplo que Usuario con un numero de identificador unico (no su POID), aqui mi la entidad usuario tendra que acceder a un dao y verificar este requerimiento.

Permitir que mis entidades hagan uso de Daos para este caso es una buena practica?, o en su defecto ponerlo en un servicio de dominio?, o mejor aun que tipo de validacion va en la entidad y cual en un servicio de dominio?

Saludos
Edgar
Saludos
Edgar

Carlos Peix

unread,
Feb 26, 2014, 9:47:27 PM2/26/14
to altnet-hispano
Hola Edgar,

Lo que yo haria, en ese caso, es hacer una validacion previa (desde la UI con una llamada Ajax) para dar feedback al usuario de que ha elegido un código ya tomado.

Luego, en mi logica, directamente grabaria la entidad esperando que la base de datos me rechace la operacion, porque seria ya una excepcion (algo no previsto) que fallara. Aunque, tambien es posible que eso ocurra en casos de concurrencia.

En general intento que mis objetos de negocios no utilicen los Daos, no siempre se puede, sin embargo.

Abrazo!

----------------------------------
Carlos Peix

Walter Poch

unread,
Feb 27, 2014, 9:53:09 AM2/27/14
to altnet-...@googlegroups.com
Gracias Carlos, devuelvo this, porque me hace más fácil la creación de objetos para los tests:

var a = new Asignacion(...).AgergarAsignacion(..).AgregarAsignacion(....).AgregarAsignacion();

El objeto a, ya me queda todo listo para los tests. Y sí, la colección "es" privada, la expongo como ICollection, pero nunca la manipulo desde afuera, por ahora soy el único developer así que lo puedo controlar, en un futuro quizás pase a un miembro private.

Edgar, yo si es una validación que necesita ir a la DB como es el caso del unique; valido en el servicio que no esté duplicado y además tengo la constraint en la DB de unique por si falla. Quizás alguien/algo mete datos por atras en la DB (en los casos de integración con otros sistemas, trabajo en una organización grande y no es fácil hacer todo con servicios con un AS400 y COBOL dando vueltas =) ).

Saludos!

Daniel Calvin

unread,
Feb 28, 2014, 7:53:18 AM2/28/14
to altnet-...@googlegroups.com
Hola Gente

Esta linda la charla, un comentario.
Yo las validaciones, y creo que es un termino muy amplio, las hago mediante el uso de specifications.
Siempre que tengo que verificar una regla de negocio implemento las specifications necesarias.


Codigo mas claro, siempre se destaca el valor conceptual en terminos de negocio.
Se reduce sobre manera la cantidad de if que se utiliza.


Ej.:
            Specification<Boleto> claseNoConsulta=SpecificationFactory.GetInstance().getBoletoClaseNoConsulta();
            Specification<Boleto> sectorNoConsulta=SpecificationFactory.GetInstance().getBoletoSectorNoConsulta();
            Specification<Boleto> apoderadoConsulta=SpecificationFactory.GetInstance().getBoletoConApoderado();
            Specification<Boleto> cuitNoConsulta=SpecificationFactory.GetInstance().getBoletoCUITNoConsulta();
            Specification<Boleto> conceptoVinculanteConsulta=SpecificationFactory.GetInstance().getBoletoVinculante();


             // Ninguna especificación es inválida según los valores de creación de b
            // El resultado debe ser false !!!!
            BoletoParaTest b=new BoletoParaTest(false,843,10,619,"33123456783");
            bool result=claseNoConsulta.or(sectorNoConsulta)
                .or(apoderadoConsulta)
                .or(cuitNoConsulta)
                .or(conceptoVinculanteConsulta)
                .isSatisfiedBy(b);
            Assert.IsFalse(result,"Se esperaba falso para esta especificación compuesta.");
            
            // La especificación apoderadoSpec es inválida segun los valores de creación de b
            // El resultado debe ser true !!!!
            b=new BoletoParaTest(true,843,10,619,"33123456783");
            result=claseNoConsulta
                .or(sectorNoConsulta)
                .or(apoderadoConsulta)
                .or(cuitNoConsulta)
                .or(conceptoVinculanteConsulta)
                .isSatisfiedBy(b);
            Assert.IsTrue(result,"Se esperaba verdadero para esta especificación compuesta, boleto con apoderado).");

            // La especificación concepto es inválida segun los valores de creación de b
            // El resultado debe ser true !!!!
            b=new BoletoParaTest(false,847,10,619,"33123456783");
            result=claseNoConsulta
                .or(sectorNoConsulta)
                .or(apoderadoConsulta)
                .or(cuitNoConsulta)
                .or(conceptoVinculanteConsulta)
                .isSatisfiedBy(b);
            Assert.IsTrue(result,"Se esperaba verdadero para esta especificación compuesta, boleto con concepto vinculante).");

Un ejemplo mas terrenal, un negocio que seguro conoce todo el mundo, relaciones familiares.
Es un ejemplito que uso siempre, se quiere determinar si person a es hemana de b y sobrina de d...
o cualquier relacion parecida:

         static void Main(string[] args)
        {
            Persona p1 = new Persona { Nombre = "Elvira", Apellido = "Bonffantino" };
            Persona p2 = new Persona { Nombre = "Fernando", Apellido = "Calvin" };

            Persona p3 = new Persona { Nombre = "Daniel", Apellido = "Calvin", Madre = p1, Padre = p2, 
                Nacimiento = new DateTime(19620505) };
            Persona p4 = new Persona { Nombre = "Fernanda", Apellido = "Calvin", Madre = p1, Padre = p2 };
            Persona p5 = new Persona { Nombre = "Silvia", Apellido = "Calvin", Madre = p1, Padre = p2 };

            Persona p6 = new Persona { Nombre = "Carlos", Apellido = "Moruzzi" };

            Persona p7= new Persona { Nombre = "Florencia", Apellido = "Moruzzi", Madre = p5, Padre = p6 };
            Persona p8 = new Persona { Nombre = "Bruno", Apellido = "Moruzzi", Madre = p5, Padre = p6 };

            Persona p9 = new Persona { Nombre = "Gerardo", Apellido = "Calvin",Padre=p3, Nacimiento =new DateTime(1989,09,12) };
            Persona p10 = new Persona { Nombre = "Matias", Apellido = "Calvin", Padre = p3, Nacimiento = new DateTime(20030402) };

            // Probando una especificacion simple
            Console.WriteLine("{0} es adulto? {1}", p9, new EsAdulto().isSatisfiedBy(p9));
            Console.WriteLine("{0} es adulto? {1}", p10, new EsAdulto().isSatisfiedBy(p10));

            // Probando algo mas complejo
            Console.WriteLine("{0} es adulto e hijo de {1}? {2}", p9,p5 ,new EsAdulto().and(new EsHijoDe(p5)).isSatisfiedBy(p9) );
            Console.WriteLine("{0} es adulto e hijo de {1}? {2}", p9, p3, new EsAdulto().and(new EsHijoDe(p3)).isSatisfiedBy(p9));

            Console.WriteLine("{0} es hijo de {1}? {2}", p8, p7, new EsHijoDe(p7).isSatisfiedBy(p8));
            Console.WriteLine("{0} es hijo de {1}? {2}", p3, p2, new EsHijoDe(p2).isSatisfiedBy(p3));

            Console.WriteLine("{0} y {1}, son hermanos? {2}", p4, p8, new EsHermanoDe(p8).isSatisfiedBy(p4));
            Console.WriteLine("{0} y {1}, son hermanos? {2}", p3, p3, new EsHermanoDe(p3).isSatisfiedBy(p3));
            Console.WriteLine("{0} y {1}, son hermanos? {2}", p4, p5, new EsHermanoDe(p5).isSatisfiedBy(p4));

            Console.WriteLine("{0} es tío de {1}? {2}", p4, p5, new EsTioDe(p4).isSatisfiedBy(p5));
            Console.WriteLine("{0} es tío de {1}? {2}", p4, p7, new EsTioDe(p7).isSatisfiedBy(p4));

        }

Esas specifications estan compuestas a su vez por otras specifications, por ejemplo:

     
    class EsHijoDe:PersonaSpecification
    {
        
        public EsHijoDe(Persona perona)
        {
            base.persona = perona;
        }

        public override bool isSatisfiedBy(Persona t)
        {
            return(t.Padre==persona || t.Madre==persona);
        }
       
    }


    public class EsHermanoDe:PersonaSpecification
    {
        public EsHermanoDe(Persona persona)
        {
            base.persona = persona;
        }

        public override bool isSatisfiedBy(Persona t)
        {
            return new EsHijoDe(t.Madre).
                or(new EsHijoDe(t.Padre)).
                isSatisfiedBy(persona) && t!=persona;
            
        }
        
    }

Espero que no les moleste, adjunto el ejemplito completo en c#

Un problema que se presentaba con esta implementación de specificatiosn es que si alguna no se cumple no podía saber cual era la que no se cumplia, para eso agregue una sobre carga al metodo is satisfiedby agrgegando como parametro una lista instanciad previamente, el metodo isstisfiedby si la la lista no es nula y la especificación no se cumple agrega un item a la lista, al ejecutar una specificacion compleja, como EsHermano, si la misma no se cumple tendría una linea agregada donde incicaria que ella no se cumplio y a su vez si otra de las invocadas no se cumple, por ejemplo EsHijoDe, se agregaria tambien.

Ej, ( este es en java )

@Service("EsDiaHabilSpecification")
@Scope("prototype")
public class EsDiaHabilSpecification 
extends SpecificationDomain<Date> 
{
@Override
public boolean isSatisfiedBy(Date t, List<String> notSatified) {
boolean result= DateHelper.esDiaHabil(t);
super.addNotSatisfied(result, 1044 ,String.format("Fecha: %1$s", t.toString() ),notSatified);
return result;
}
}

Otro, compuesto..

public class ContraAsientoValidoSpecification
extends SpecificationDomain<SolicitudArbitraje> 
{

@Override
public boolean isSatisfiedBy(SolicitudArbitraje t, List<String> notSatified) {
boolean result = true;
ProcesoBatchActivoSpecification porocesoBatchActivo = new ProcesoBatchActivoSpecification();
//Validar el estado de la solicitud (5)
result = t.getEstado() == EstadoDeSolicitud.AUTORIZADA_OP_PENDIENTE;
result = porocesoBatchActivo.isSatisfiedBy(null, notSatified) && result;
super.addNotSatisfied(result,1732,String.format("Estado de solicitud incorrecto para contra asentar:  %1$s",  t.getEstado().toString()),notSatified);
return result;
}

En java, utilizo mucho spring data, le paso specifications a los repositorios y en base a ellas se arman dinamicamente los criterias para armar queries desde JPA. ( por debajo hibernate )

Esta muy bueno, el codigo es limpio, el lenguaje del negocio, del dominio esta muy presente en el código.
Se lee con facilidad, no solo por las entidades, las reglas de negocio se expresan en el lenguaje del dominio.

Estaría bueno conocer sus opiniones o experiencias....

Daniel
Daniel A. Calvin
Cooperator Team Member

EspecificacionesEjeplo.zip

Carlos Admirador

unread,
Aug 14, 2020, 7:52:58 AM8/14/20
to AltNet-Hispano

Qué tal https://fluentassertions.com/ ?

Fluent Assertions is a set of .NET extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit test.

También aplica a reglas de negocio ??

To verify that a particular business rule is enforced using exceptions.

var recipe = new RecipeBuilder()
                    .With(new IngredientBuilder().For("Milk").WithQuantity(200, Unit.Milliliters))
                    .Build();
Action action = () => recipe.AddIngredient("Milk", 100, Unit.Spoon);
action
                    .Should().Throw<RuleViolationException>()
                    .WithMessage("*change the unit of an existing ingredient*")
                    .And.Violations.Should().Contain(BusinessRule.CannotChangeIngredientQuantity


Carlos Admirador

unread,
Aug 18, 2020, 2:09:02 AM8/18/20
to AltNet-Hispano
Daniel, puedes contarnos más en profundidad?
Comentas que "pasas specifications a los repositorios y en base a ellas se arman dinamicamente los criterias para armar queries".

Las validaciones se refieren a niveld e negocio: "las reglas de negocio se expresan en el lenguaje del dominio."


Saludos.
Reply all
Reply to author
Forward
0 new messages