Entity Framework y Specification Pattern

Introducción

Hace ya unos cuantos dias que vengo dándole vueltas a la cabeza para escribir un post sobre especificaciones y como construirlas sobre la infraestructura de Entity Framework, aunque en realidad sería igual sobre cualquier elemento IQueryable, aunque con alguna nota de implementación. Pensando en esta entrada no sabía si comenzar con una explicación sobre este patrón o ceñirme directamente a una posible implementación,  puesto que, me parece magnifica la documentación sobre el mismo que Fowler y Evans tienen en el documento de referencia. Al final, y despues de darle muchas vueltas me quedaré entre medias puesto que creo que es necesario realizar una pequeña introducción, aunque aconsejo encarecidamente desde aquí leerse el documento anterior si nunca ha visto información acerca de este patrón.

Sintetizándolo, aunque sea demasiada síntesis, podríamos decir que el patrón espeficación trata de lograr una separación entre la sentencia que unos objetos deberían de cumplir y el objeto que realiza la selección. Por ejemplo una especificación podría describir “los clientes que pertenecen a una determinada situación geográfica” pero no saber nada acerca de como realizar dicha selección. En definitiva no se trata nada más y nada menos que agregar una responsabilidad desacoplada de los objetos de dominio que la usan.

Este patrón es muy escuchado y usado en arquitecturas basadas en modelos de dominio y puesto en práctica en muchas arquitecturas para definir “criterios” de selección. Basándonos en la definición formal de este patrón, mostrada en la Figura 1, podríamos ver a primeras que una implementación de este patrón trabajando con IQueryable no tendría mucho sentido.

 

Specification_UML

La razón principal de la afirmación anterior viene de la propia definición del patron, la cual implica trabajar con objetos directamente en memoria puesto que el método IsSatisfiedBy tomaría una instancia del objeto en el cual queremos comprobar si cumple un determinado criterio y devolver true o false según se cumpla o no, algo que por supuesto no deseamos por la sobrecarga que esto implicaría. Por todo esto podríamos modificar un poco nuestra definición de Specification para que en vez de devolver un booleano negando o afirmando el cumplimiento de una especificación determinada podríamos devolver una “expression” con el criterio a cumplir. En el siguiente fragmento de código tendríamos un esqueleto de nuestro contrato base con esta ligera modificación.

    /// <summary>
    /// Base contract for Specification pattern, for more information
    /// about this pattern see http://martinfowler.com/apsupp/spec.pdf
    /// or http://en.wikipedia.org/wiki/Specification_pattern.
    /// Really this is variant implementation for add feature of linq and
    /// lambda expression into this pattern.
    /// </summary>
    /// <typeparam name="TEntity">Type of entity</typeparam>
    public interface ISpecification<TEntity>
        where TEntity : class,new()
    {
        /// <summary>
        /// Check if this specification is satisfied by a 
        /// specific expression lambda
        /// </summary>
        /// <returns></returns>
        Expression<Func<TEntity, bool>> SatisfiedBy();
    }

Profundizando

Llegados a este punto podríamos decir que ya tenemos la base y la idea de lo que queremos construir, ahora, solamente falta seguir las propias normas y guias de este patrón empezándonos a crear nuestras especificaciones directas o “hard coded specifications” y nuestras especificaciones compuestas, al estilo And, Or …

Según avanzas en esta aproximación uno se da cuenta de que tendrá que realizar un buen esfuerzo de trabajo con árboles de expresiones, algo en el que no todo el mundo está muy puesto, y a lo que se le tiene algo de miedo, del que uno se puede curar leyendo el excelente libro de mi amigo y colega Octavio :-)

Mi primera intentona, y fallida a mi pesar, era similar a lo siguiente:

    /// <summary>
    /// A logic AND Specification
    /// </summary>
    /// <typeparam name="T">Type of entity that check this specification</typeparam>
    public class AndSpecification<T>
       : CompositeSpecification<T>
       where T : class,new()
    {
        #region Members

        private ISpecification<T> _RightSideSpecification = null;
        private ISpecification<T> _LeftSideSpecification = null;

        #endregion

        #region Public Constructor

        /// <summary>
        /// Default constructor for AndSpecification
        /// </summary>
        /// <param name="leftSide">Left side specification</param>
        /// <param name="rightSide">Right side specification</param>
        public AndSpecification(ISpecification<T> leftSide, ISpecification<T> rightSide)
        {
            if (leftSide == (ISpecification<T>)null)
                throw new ArgumentNullException("leftSide");

            if (rightSide == (ISpecification<T>)null)
                throw new ArgumentNullException("rightSide");

            this._LeftSideSpecification = leftSide;
            this._RightSideSpecification = rightSide;
        }

        #endregion

        #region Composite Specification overrides

        /// <summary>
        /// Left side specification
        /// </summary>
        public override ISpecification<T> LeftSideSpecification
        {
            get { return _LeftSideSpecification; }
        }

        /// <summary>
        /// Right side specification
        /// </summary>
        public override ISpecification<T> RightSideSpecification
        {
            get { return _RightSideSpecification; }
        }

        /// <summary>
        /// <see cref="Microsoft.Samples.NLayerApp.Domain.Core.Specification.ISpecification"/>
        /// </summary>
        /// <returns><see cref="Microsoft.Samples.NLayerApp.Domain.Core.Specification.ISpecification"/></returns>
        public override Expression<Func<T, bool>> SatisfiedBy()
        {
            Expression<Func<T, bool>> left = _LeftSideSpecification.SatisfiedBy();
            Expression<Func<T, bool>> right = _RightSideSpecification.SatisfiedBy();

            InvocationExpression invokedExpr = Expression.Invoke(right, left.Parameters.Cast<Expression>());
            return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(left.Body, invokedExpr), left.Parameters);
        }

        #endregion
    }

Os podreís imaginar que la especificacion OR era similar a esta, simplemente modificando Expression.AndAlso por Expression.Or. Todo funcionaba de maravilla en mis test contra mi mock de los ObjectSet de Entity Framework hasta que pasando las pruebas de integración me di cuenta que el QueryProvider de Entity Framework no soportaba el método Invoke, al contrario que un QueryProvider sobre objetos en memoria, y que por lo tanto esta forma no me servía :-(.

Aunque ahora mismo pondré la solución fijaros como, de una forma elegante, manteniendo el principio de separación de responsabilidades y dejando un concepto de negocio como es un tipo especial de búsqueda perfectamente explícito, se podrían declarar especificaciones como la siguiente

NOTA: Seguramente que alguna vez ha pensado como hacer consultas distintas en función de parámetros usando conjunciones o disjunciones de expressiones, pues esta es una posible solución.

 

    /// <summary>
    /// AdHoc specification for finding orders
    /// by shipping values
    /// </summary>
    public class OrderShippingSpecification
        :Specification<Order>
    {
        #region Members

        string _ShippingName = default(String);
        string _ShippingAddress = default(String);
        string _ShippingCity = default(String);
        string _ShippingZip = default(String);

        #endregion

        #region Constructor

        /// <summary>
        /// Default constructor for this specification
        /// </summary>
        /// <param name="shippingName">Shipping Name or null for not include this value in search</param>
        /// <param name="shippingAddress">Shipping Address or null for not include this vlaue in search</param>
        /// <param name="shippingCity">Shipping City or null for not include this value in search</param>
        /// <param name="shippingZip">Shipping Zip or null for not include this value in search</param>
        public OrderShippingSpecification(string shippingName,string shippingAddress,string shippingCity,string shippingZip)
        {
            _ShippingName = shippingName;
            _ShippingAddress = shippingAddress;
            _ShippingCity = shippingCity;
            _ShippingZip = shippingZip;
        }

        #endregion

        #region Specification Overrides

        /// <summary>
        /// <see cref=" Microsoft.Samples.NLayerApp.Domain.Core.Specification.Specification{TEntity}"/>
        /// </summary>
        /// <returns><see cref=" Microsoft.Samples.NLayerApp.Domain.Core.Specification.Specification{TEntity}"/></returns>
        public override System.Linq.Expressions.Expression<Func<Order, bool>> SatisfiedBy()
        {
            Specification<Order> beginSpec = new TrueSpecification<Order>();

            if (_ShippingName != null)
                beginSpec &= new DirectSpecification<Order>(o =>o.ShippingName!=null &&  o.ShippingName.Contains(_ShippingName));

            if (_ShippingAddress != null)
                beginSpec &= new DirectSpecification<Order>(o => o.ShippingAddress !=null && o.ShippingAddress.Contains(_ShippingAddress));

            if (_ShippingCity != null)
                beginSpec &= new DirectSpecification<Order>(o => o.ShippingCity != null && o.ShippingCity.Contains(_ShippingCity));

            if (_ShippingZip != null)
                beginSpec &= new DirectSpecification<Order>(o => o.ShippingZip != null && o.ShippingZip.Contains(_ShippingZip));

            return beginSpec.SatisfiedBy();

        }

        #endregion
    }

Una posible solución para especificaciones And y OR

Os podreís imaginar que seguramente existe más de una aproximación para este tema y que yo probablemente me haya decantado por una aproximación algo dura, pero la verdad es que me parecía la más adecuada. Releyendo algún post sobre el tema me acorde de la serie que Matt Warren tenía sobre el tema y como hacía uso del patrón Visitor para evaluar las expresiones, ExpressionVisitor. Además, navegando, me encontré con un ejemplo aceptable que resolvía este tema en el blog de Colling Meek el cual fué la solución que adopte.

Dada la explicación, lo que necesitamos es la siguiente clase que nos haga una recomposición de las expressiones en vez de un InvocationExpression, esta clase de apoyo es la siguiente:

    /// <summary>
    /// Extension methods for add And and Or with parameters rebinder
    /// </summary>
    public static class ExpressionBuilder
    {
        public static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // build parameter map (from parameters of second to parameters of first)
            var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with parameters from the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);
            // apply composition of lambda expression bodies to parameters from the first expression 
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.And);
        }
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.Or);
        }

    }
 

La definición completa por lo tanto de una especificación And queda como sigue:

    /// <summary>
    /// A logic AND Specification
    /// </summary>
    /// <typeparam name="T">Type of entity that check this specification</typeparam>
    public class AndSpecification<T>
       : CompositeSpecification<T>
       where T : class,new()
    {
        #region Members

        private ISpecification<T> _RightSideSpecification = null;
        private ISpecification<T> _LeftSideSpecification = null;

        #endregion

        #region Public Constructor

        /// <summary>
        /// Default constructor for AndSpecification
        /// </summary>
        /// <param name="leftSide">Left side specification</param>
        /// <param name="rightSide">Right side specification</param>
        public AndSpecification(ISpecification<T> leftSide, ISpecification<T> rightSide)
        {
            if (leftSide == (ISpecification<T>)null)
                throw new ArgumentNullException("leftSide");

            if (rightSide == (ISpecification<T>)null)
                throw new ArgumentNullException("rightSide");

            this._LeftSideSpecification = leftSide;
            this._RightSideSpecification = rightSide;
        }

        #endregion

        #region Composite Specification overrides

        /// <summary>
        /// Left side specification
        /// </summary>
        public override ISpecification<T> LeftSideSpecification
        {
            get { return _LeftSideSpecification; }
        }

        /// <summary>
        /// Right side specification
        /// </summary>
        public override ISpecification<T> RightSideSpecification
        {
            get { return _RightSideSpecification; }
        }

        /// <summary>
        /// <see cref="Microsoft.Samples.NLayerApp.Domain.Core.Specification.ISpecification"/>
        /// </summary>
        /// <returns><see cref="Microsoft.Samples.NLayerApp.Domain.Core.Specification.ISpecification"/></returns>
        public override Expression<Func<T, bool>> SatisfiedBy()
        {
            Expression<Func<T, bool>> left = _LeftSideSpecification.SatisfiedBy();
            Expression<Func<T, bool>> right = _RightSideSpecification.SatisfiedBy();

            return (left.And(right));
         }

        #endregion
    }
 

¿Por dónde continuamos?

Dentro de la jerarquía de especificaciones que se propone en el documento de Eric y Fowler podemos encontrar desde la especificación Not hasta una base para LeafSpecifications que tendríamos que construir… es decir, un poquito más de curro para poder disfrutar…

 

Un resumen

La verdad espero que le encontréis utilidad a esta forma de implementar el patrón Specification, y por supuesto, si no lo conociais espero que os levante un poco la curiosidad y veais las bondades que este puede tener  dentro de vuestros desarrollos, por supuesto me encantaría ver vuestras opiniones al respecto y si realmente esta forma de encajar criterios es adecuado para vuestras soluciones…

 

Saludos

Unai Zorrilla

 

 

Published 9/2/2010 19:37 por Unai
Comparte este post:
http://geeks.ms/blogs/unai/archive/2010/02/09/entity-framework-y-specification-pattern.aspx

Comentarios

# re: Entity Framework y Specification Pattern

Excelente post Unai!!

Qué bueno que te sirviera de ayuda un post de Colin, es mi vecino al otro lado del pasillo en la oficina... Ya le he dado las gracias de tu parte. :-P

Vendras al Summit?

M.

Tuesday, February 09, 2010 11:19 PM por Miguel LLopis

# re: Entity Framework y Specification Pattern

Joder Miguel, que casualidad tio!, dale las gracias y dile para lo que ha servido :-). Este año tengo curro y no puedo, tenga ya a la family bastante abandonada :-(

Unai

Wednesday, February 10, 2010 1:27 AM por Unai

# Generando búsquedas mediante el patrón especificación

Como ya comenté en mi anterior post, llevo un tiempo trabajando sobre la arquitectura DDD NLayer

Friday, July 02, 2010 12:51 AM por rjimenez

# re: Entity Framework y Specification Pattern

Hola Unai, hace poco que estoy estudiando la guia de arquitectura de n-capas, y es un excelente libro, compre tu libro de Entity es espectacular me ayudado a entender mas la capa de infraestructura, y he seguido algunos de los tutoriales en c# que has hecho, y me parecen excelentes, muy claros, pues necesito de tu ayuda he estado mirando sobre el patron especificacion y sobre los arboles de expresion, y no he logrado entenderlos muy bien,

trate de comprar el libro que recomiendas de c#3.0 y linq de octavio( pero al parecer no lo hay en pdf ya hice la consulta a Krasis, estoy esperando la respuesta ya que vivo en colombia entonces me queda complicado comprar el impreso) para comprender los arboles de expresiones, bueno aca va el favor me puedes explicar estos temas con un ejemplo si es posible con un video asi de claros como los que hiciste para los tutoriales de c#, seria estupendo hermano se lo agradeceria en el alma, pues no siendo mas me despido y quiero agradecerte por todo el conocimiento que das ya que ayuda mucho

Thursday, March 31, 2011 6:52 AM por Juio