albertcapdevila.netProgramación en .NEThttp://albertcapdevila.net/© 2024 - albertcapdevila.net2024-03-19T10:31:35ZAlbert Capdevilahttp://albertcapdevila.net/favicon.icohttps://albertcapdevila.net/entidad-domain-driver-design/Qué es una Entidad de Domain Driver Design y cómo se implementa en c# y .net <p><a href="/entidad-domain-driver-design"><img alt="Entidad de Domain Driven Design única y perdurable en el tiempo" class="img-responsive" src="https://storagequedat.blob.core.windows.net/contenedorblog/298c94aa-d7f1-42d4-a758-803f1270d389_entidad.jpg" /></a></p>
<p>Foto de <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/es/@vivalaveronica?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1654350570119000&usg=AOvVaw2-hx2uzpmk04xJFVye8a8H" href="https://unsplash.com/es/@vivalaveronica?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Veronica Benavides</a> en <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/es/s/fotos/unique?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1654350570119000&usg=AOvVaw2F8PjTygIZtqjMAhEn-2Qu" href="https://unsplash.com/es/s/fotos/unique?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Unsplash</a></p>
<hr />
<p>En este artículo voy a describir qué es una Entidad desde el punto de vista de Domain Driven Design, qué características tiene y cómo implementarla en c#.net.</p>
2022-06-04T00:00:00Z2022-06-04T06:35:08ZAlbert Capdevilahttp://albertcapdevila.net<p><a href="/entidad-domain-driver-design"><img alt="Entidad de Domain Driven Design única y perdurable en el tiempo" class="img-responsive" src="https://storagequedat.blob.core.windows.net/contenedorblog/298c94aa-d7f1-42d4-a758-803f1270d389_entidad.jpg" /></a></p>
<p>Foto de <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/es/@vivalaveronica?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1654350570119000&usg=AOvVaw2-hx2uzpmk04xJFVye8a8H" href="https://unsplash.com/es/@vivalaveronica?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Veronica Benavides</a> en <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/es/s/fotos/unique?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1654350570119000&usg=AOvVaw2F8PjTygIZtqjMAhEn-2Qu" href="https://unsplash.com/es/s/fotos/unique?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Unsplash</a></p>
<hr />
<p>En este artículo voy a describir qué es una Entidad desde el punto de vista de Domain Driven Design, qué características tiene y cómo implementarla en c#.net.</p>
<style type="text/css">/* Background */ .chroma { background-color: #fafafa }
/* Error */ .chroma .err {
background-color: #fdd;
}
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #e5e5e5 }
/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* Keyword */ .chroma .k { color: #0000ff }
/* KeywordConstant */ .chroma .kc { color: #0000ff }
/* KeywordDeclaration */ .chroma .kd { color: #0000ff }
/* KeywordNamespace */ .chroma .kn { color: #0000ff }
/* KeywordPseudo */ .chroma .kp { color: #0000ff }
/* KeywordReserved */ .chroma .kr { color: #0000ff }
/* KeywordType */ .chroma .kt { color: #2b91af }
/* NameClass */ .chroma .nc { color: #2b91af }
/* LiteralString */ .chroma .s { color: #a31515 }
/* LiteralStringAffix */ .chroma .sa { color: #a31515 }
/* LiteralStringBacktick */ .chroma .sb { color: #a31515 }
/* LiteralStringChar */ .chroma .sc { color: #a31515 }
/* LiteralStringDelimiter */ .chroma .dl { color: #a31515 }
/* LiteralStringDoc */ .chroma .sd { color: #a31515 }
/* LiteralStringDouble */ .chroma .s2 { color: #a31515 }
/* LiteralStringEscape */ .chroma .se { color: #a31515 }
/* LiteralStringHeredoc */ .chroma .sh { color: #a31515 }
/* LiteralStringInterpol */ .chroma .si { color: #a31515 }
/* LiteralStringOther */ .chroma .sx { color: #a31515 }
/* LiteralStringRegex */ .chroma .sr { color: #a31515 }
/* LiteralStringSingle */ .chroma .s1 { color: #a31515 }
/* LiteralStringSymbol */ .chroma .ss { color: #a31515 }
/* OperatorWord */ .chroma .ow { color: #0000ff }
/* Comment */ .chroma .c { color: #008000 }
/* CommentHashbang */ .chroma .ch { color: #008000 }
/* CommentMultiline */ .chroma .cm { color: #008000 }
/* CommentSingle */ .chroma .c1 { color: #008000 }
/* CommentSpecial */ .chroma .cs { color: #008000 }
/* CommentPreproc */ .chroma .cp { color: #0000ff }
/* CommentPreprocFile */ .chroma .cpf { color: #0000ff }
/* GenericEmph */ .chroma .ge { font-style: italic }
/* GenericHeading */ .chroma .gh { font-weight: bold }
/* GenericPrompt */ .chroma .gp { font-weight: bold }
/* GenericStrong */ .chroma .gs { font-weight: bold }
/* GenericSubheading */ .chroma .gu { font-weight: bold }
</style>
<h4></h4>
<h4>1. Definición de Entidad</h4>
<p>Una entidad es una clase que representa un concepto de la <a href="/logica-negocio-programacion/" target="_blank">capa de negocio</a> de la aplicación. Dicho concepto debe ser único y perdurable en el tiempo.</p>
<p>Así lo define Eric Evans en <em>Domain Driven Design</em>:</p>
<blockquote>
<p>Muchos objetos no se definen por sus atributos sino por su continuidad en el tiempo y por su identidad.</p>
<p><em>Eric Evans. Domain Drivern Design.</em></p>
</blockquote>
<p>A nivel práctico significa que una Entidad contiene un identificador único. Ese identificador es una propiedad inmutable que solemos representar en c# con un <em>int</em>, <em>long,</em> <em>guid </em>o<em> string</em>. Es posible tener dos Entidades con diferentes atributos (propiedades en c#), pero si su identificador es el mismo, se trata de la misma Entidad.</p>
<p>Cada aplicación tiene Entidades propias relacionadas con su negocio. Por ejemplo, en un CRM, una Entidad podría ser un Cliente, y en una aplicación de facturas, una Factura.</p>
<p>En c# no tenemos ningún elemento de serie que sea capaz de cumplir con este comportamiento.</p>
<p></p>
<h4>2. Comparando objetos en c#</h4>
<p>Imagina estas tres variables que representan el mismo concepto <em>persona </em>en c#:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">persona</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">mismaPersona</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">otraPersona</span> <span class="p">=</span> <span class="m">2</span><span class="p">;</span></span></span></code></pre>
<p>Si empezamos a realizar comparaciones entre ellas todo va a funcionar como esperamos:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">persona</span> <span class="p">==</span> <span class="n">mismaPersona</span><span class="p">)</span> <span class="c1">// true</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/* Código a ejecutar*/</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">persona</span> <span class="p">==</span> <span class="n">otroPersona</span><span class="p">)</span> <span class="c1">// false</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/* Este código no se ejecuta*/</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
<p>Esto es así porque <strong><em>int </em></strong>es un <em>tipo de valor </em>y cuando se compara con otra variable de tipo <strong><em>int</em></strong><strong><em> </em></strong>se verifica si el valor que contienen es el mismo. A este concepto se le llama <strong>comparación por valor</strong>.</p>
<p>Veamos qué pasa si en lugar de enteros utilizamos clases.</p>
<p>Primero creo la clase Persona:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">class</span> <span class="nc">Persona</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="kt">int</span> <span class="n">Id</span> <span class="p">{ </span><span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;}</span>
<span class="p">}</span></span></span></code>
</pre>
<p>Ahora aplico el mismo código que antes:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="c1">// Inicializo igual que antes</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">persona</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Persona</span> <span class="p">{</span> <span class="n">Id</span><span class="p">:</span> <span class="m">1</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">mismaPersona</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Persona</span> <span class="p">{</span> <span class="n">Id</span><span class="p">:</span> <span class="m">1</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">otraPersona</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Persona</span> <span class="p">{</span> <span class="n">Id</span><span class="p">:</span> <span class="m">2</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Aplico las mismas comparaciones</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">persona</span> <span class="p">==</span> <span class="n">mismaPersona</span><span class="p">)</span> <span class="c1">// false ¿? -> antes era true</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/* Este código no se ejecuta */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">persona</span> <span class="p">==</span> <span class="n">otroPersona</span><span class="p">)</span> <span class="c1">// false</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/* Este código no se ejecuta */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre>
<p>Ahora la comparativa entre <em>persona </em>y <em>mismaPersona </em>es diferente. No se compara el valor de la implementación sino la dirección de memoria donde se encuentra la variable, por eso el resultado es falso. Son objetos que se encuentran en diferentes posiciones de memoria.</p>
<p>A este concepto se llama <strong>comparación por referencia</strong>.</p>
<blockquote>
<p>Dos clases son iguales si son el mismo objeto en memoria, no si tienen los mismos valores.</p>
<p>Dos enteros son iguales si tienen el mismo valor, aunque sean objetos diferentes en memoria. </p>
</blockquote>
<p>Nota*: Los <em>strings </em>son tipos por referencia, pero se comparan por valor.</p>
<p></p>
<h4>3. Clase base Entidad en c#</h4>
<p>Existe un paquete <em>nuget </em><a href="https://github.com/vkhorikov/CSharpFunctionalExtensions" target="_blank">CSharpFunctionalExtensions</a> de <a href="https://enterprisecraftsmanship.com/" target="_blank">Vladimir Khorikov</a> que contiene una clase base diseñada para representar una Entidad.</p>
<p>En su post <a href="https://enterprisecraftsmanship.com/posts/entity-base-class/" target="_blank">Entity Base Class</a> explica los detalles que permiten conseguir el comportamiento (te recomiendo su blog). Básicamente lo que hace esta clase es sobrescribir los métodos <em>Equals</em> y <em>GetHashCode</em>.</p>
<p>Usando esta librería podemos convertir la clase <em>Persona</em> en una entidad de la siguiente manera:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">class</span> <span class="nc">Persona</span> <span class="p">:</span> <span class="n">Entity</span><span class="p"><</span><span class="kt">int</span><span class="p">> </span><span class="c1">// heredamos de Entity</span>
<span class="p">{</span>
<span class="k">public</span> <span class="n">Persona</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">id</span><span class="p">) </span><span class="c1">// pasa el identificador a la clase base</span>
<span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
<p>Ahora al comparar dos clases con el mismo identificador el resultado es <em>true</em>.</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">persona</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Persona</span><span class="p">(</span><span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">mismaPersona</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Persona</span><span class="p">(</span><span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">otraPersona</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Persona</span><span class="p">(</span><span class="m">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">persona</span> <span class="p">==</span> <span class="n">mismaPersona</span><span class="p">)</span> <span class="c1">// ahora sí es true :)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">persona</span> <span class="p">==</span> <span class="n">otroPersona</span><span class="p">)</span> <span class="c1">// false</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
<p>A este concepto se le llama <strong>comparación por identidad. </strong></p>
<blockquote>
<p>Dos objetos son iguales si tienen el mismo identificador, independientemente del resto de propiedades. </p>
</blockquote>
<p>Hasta aquí hemos conseguido cumplir con la característica principal de las entidades, pero no la única.</p>
<p></p>
<h4>4. Las propiedades de una Entidad tienen el<em> set </em> privado</h4>
<p><strong>Todas las propiedades</strong> de la Entidad deben tener el <em>set </em><strong>privado o directamente no tener un <em>set</em></strong>. Para modificar dichas propiedades se usan los métodos.</p>
<p>Necesitamos que sea así para controlar las pre-condiciones y post-condiciones que se deben cumplir antes de realizar cualquier cambio en la clase. Y de regalo centralizamos las modificaciones de las propiedades en un único lugar, evitando duplicidades de código.</p>
<p>Por ejemplo, siguiendo con Persona, si le añadimos las propiedades Nombre y Apellidos, inicialmente lo haríamos así:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">class</span> <span class="nc">Persona</span> <span class="p">:</span> <span class="n">Entity</span><span class="p"><</span><span class="kt">int</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="n">Persona</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
<span class="line"><span class="cl">
</span></span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Nombre</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// así lo haríamos todos inicialmente
</span><span class="k"> public</span> <span class="kt">string</span> Apellidos <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span></span></span></code></pre>
<p>El problema de esta implementación es que puede generar inconsistencias, estados no permitidos de nuestro negocio. Por ejemplo, podría realizar este destrozo:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">persona</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Persona</span><span class="p">(</span><span class="m">1</span><span class="p">);
</span><span class="n">persona</span><span class="p">.</span><span class="n">Nombre</span> <span class="p">=</span> <span class="s">"Albert"</span><span class="p">; </span><span class="c1">// inicialización "correcta"</span>
<span class="n">persona</span><span class="p">.</span><span class="n">Apellidos</span> <span class="p">=</span> <span class="s">"Capdevila"</span><span class="p">; </span>
<span class="c1">// ... código ... //</span>
<span class="n">persona</span><span class="p">.</span><span class="n">Nombre</span> <span class="p">=</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">; </span><span class="c1">// asignar un nombre vacío</span>
<span class="n">persona</span><span class="p">.</span><span class="n">Apellidos</span> <span class="p">=</span> <span class="s">"1"</span><span class="p">; </span><span class="c1">// esté apellido no tiene sentido</span></span></span></code></pre>
<p>En cambio si dejamos los <em>set </em>privados, podremos controlar mejor si se intenta establecer una propiedad con un valor incorrecto:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">class</span> <span class="nc">Persona</span> <span class="p">:</span> <span class="n">Entity</span><span class="p"><</span><span class="kt">int</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="n">Persona</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="kt">string</span> <span class="n">Nombre</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// set privado</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Apellidos</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">} </span><span class="c1">// set privado</span>
<span class="line"><span class="cl">
</span></span> <span class="k">public</span> <span class="k">void</span> <span class="n">CambiarNombre</span><span class="p">(</span><span class="kt">string</span> <span class="n">nombre</span><span class="p">,</span> <span class="kt">string</span> <span class="n">apellidos</span><span class="p">)</span>
<span class="p">{
</span><span class="c1">// Validaciones previas</span>
<span class="k">if</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">nombre</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">throw</span> <span class="k">new</span> <span class="n">Exception</span><span class="p">(</span><span class="s">"Indica el nombre de la persona"</span><span class="p">); </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">if</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">apellidos</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">throw</span> <span class="k">new</span> <span class="n">Exception</span><span class="p">(</span><span class="s">"Indica los apellidos de la persona"</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">Nombre</span> <span class="p">=</span> <span class="n">nombre</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">Apellidos</span> <span class="p">=</span> <span class="n">apellidos</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
<p>En la fase de validaciones previas podríamos añadir todas las que se nos ocurran. Por ejemplo validaciones de longitud, o para evitar que pongan símbolos o números.</p>
<p>Lanzar excepciones cuando el valor no es válido está bien pero es un poco radical. Podríamos devolver un "resultado" que nos indique si se ha podido realizar la operación. Para ello podemos usar la clase <em>Result </em>que se encuentra en la misma librería <a href="https://github.com/vkhorikov/CSharpFunctionalExtensions" target="_blank">CSharpFunctionalExtensions</a>.</p>
<p><em>Result</em> es un <em>struct</em> que contiene una resultado válido o un mensaje de error.</p>
<p>Modifico el método CambiarNombre para que devuelva <em>Result </em>en lugar de <em>void.</em></p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">class</span> <span class="nc">Persona</span> <span class="p">:</span> <span class="n">Entity</span><span class="p"><</span><span class="kt">int</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="n">Persona</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
<span class="line"><span class="cl">
</span></span> <span class="k">public</span> <span class="kt">string</span> <span class="n">Nombre</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// set privado</span>
<span class="k">public</span> <span class="kt">string</span> <span class="n">Apellidos</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">} </span><span class="c1">// set privado</span>
<span class="line"><span class="cl">
</span></span> <span class="k">public</span> <span class="n">Result</span> <span class="n">CambiarNombre</span><span class="p">(</span><span class="kt">string</span> <span class="n">nombre</span><span class="p">,</span> <span class="kt">string</span> <span class="n">apellidos</span><span class="p">) </span><span class="c1">// devuelve Result para evitar las Excepciones</span>
<span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">nombre</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">Result</span><span class="p">.</span><span class="n">Failure</span><span class="p">(</span><span class="s">"Indica el nombre de la persona"</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">if</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">apellidos</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">Result</span><span class="p">.</span><span class="n">Failure</span><span class="p">(</span><span class="s">"Indica los apellidos de la persona"</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">Nombre</span> <span class="p">=</span> <span class="n">nombre</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">Apellidos</span> <span class="p">=</span> <span class="n">apellidos</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">Result</span><span class="p">.</span><span class="n">Success</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
<p>Veamos cómo se usaría:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">persona</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Persona</span><span class="p">(</span><span class="m">1</span><span class="p">);
</span><span class="n">persona</span><span class="p">.</span><span class="n">Nombre</span> <span class="p">=</span> <span class="s">"Albert"</span><span class="p">; </span><span class="c1">// ¡¡no compila!!</span>
<span class="n">persona</span><span class="p">.</span><span class="n">Apellidos</span> <span class="p">=</span> <span class="s">"Capdevila"</span><span class="p">; </span><span class="c1">// ¡¡no compila!!
</span>
<span class="c1">// Hay que usar el método en su lugar //</span>
<span class="n">persona</span><span class="p">.</span><span class="n">CambiarNombre(</span><span class="s">"Albert"</span><span class="p">,</span><span class="s">"Capdevila"</span><span class="p">);
</span><span class="n">persona</span><span class="p">.</span><span class="n">CambiarNombre(</span><span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">,</span><span class="s">"Capdevila"</span><span class="p">); </span><span class="c1">// no se conseguiría el cambio</span>
</span></span></code></pre>
<p>La primera reacción al explicar que los <em>sets </em>de una entidad deben ser privados siempre es de sorpresa. Inicialmente el código es más largo y parece menos flexible. Pero en realidad, esas líneas de código de más, si no las metemos dentro de la entidad, acabarán existiendo fuera de ella y además las iremos duplicando por diferentes puntos. Con el paso del tiempo acabarán tan esparcidas por el código que nos será complicado entender las reglas de negocio.</p>
<p></p>
<p></p>
<ul>
</ul>
<p><span style="color: rgb(0, 0, 0); font-family: OswaldBold; font-size: 18px; text-transform: uppercase;"> 5. El estado de una entidad simpre debe ser válido</span></p>
<p>Las propiedades de la Entidad siempre deben estar en un estado válido. Por ejemplo, si definimos una propiedad <em>Descripción </em>de tipo <em>string </em>con una longitud máxima de 64 caracteres, no debe ser posible asignar un valor con una longitud mayor. </p>
<p>Imaginemos que en el ejemplo de Persona, definimos que Nombre y Apellidos son obligatorias siempre. Pues bien, resulta que tan solo con instanciar un objeto Persona ya no cumplimos con los requisitos.</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">persona</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Persona</span><span class="p">(</span><span class="m">1</span><span class="p">);
</span><span class="c1">// en este punto Nombre y Apellidos son null</span></span></span></code></pre>
<p>Por tanto, nos falta alguna cosa para evitar que pueda existir una Persona en un estado no válido.</p>
<p>Voy a modificar el constructor de Persona para evitar estados inválidos:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">class</span> <span class="nc">Persona</span> <span class="p">:</span> <span class="n">Entity</span><span class="p"><</span><span class="kt">int</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="n">Persona</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">,</span> <span class="kt">string</span> <span class="n">nombre</span><span class="p">,</span> <span class="kt">string</span> <span class="n">apellidos</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">nombre</span><span class="p">)</span>
<span class="k">throw</span> <span class="k">new</span> <span class="n">Exception</span><span class="p">(</span><span class="s">"Indica el nombre de la persona"</span><span class="p">); </span>
<span class="line"><span class="cl">
</span></span> <span class="k">if</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">apellidos</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Result</span><span class="p">.</span><span class="n">Failure</span><span class="p">(</span><span class="s">"Indica los apellidos de la persona"</span><span class="p">);</span>
<span class="line"><span class="cl">
</span></span> <span class="n">Nombre</span> <span class="p">=</span> <span class="n">nombre</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">Apellidos</span> <span class="p">=</span> <span class="n">apellidos</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="c1">// Propiedades y métodos</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
<p>Con esto ya conseguimos el propósito deseado, pero otra vez estamos lanzando excepciones que podríamos evitar.</p>
<p>Casi en el 90% de entidades que diseño acabo usando un método factoría simple de la siguiente manera:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">class</span> <span class="nc">Persona</span> <span class="p">:</span> <span class="n">Entity</span><span class="p"><</span><span class="kt">int</span><span class="p">></span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="c1">// -> método factoría simple que protege el estado inicial de la entidad</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="k">static</span> <span class="n">Result</span><span class="p"><</span><span class="n">Persona</span><span class="p">></span> <span class="n">Crear</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">,</span> <span class="kt">string</span> <span class="n">nombre</span><span class="p">,</span> <span class="kt">string</span> <span class="n">apellidos</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">var</span> <span class="n">validacion</span> <span class="p">=</span> <span class="n">Result</span><span class="p">.</span><span class="n">Combine</span><span class="p">( </span><span class="c1">// -> Result.Combine concatena los métodos de validación</span>
<span class="n">ValidarNombre</span><span class="p">(</span><span class="n">nombre</span><span class="p">),</span>
<span class="n">ValidarApellidos</span><span class="p">(</span><span class="n">apellidos</span><span class="p">) </span><span class="c1">// -> Podemos concatenar las validaciones que queramos</span>
<span class="p"> );</span>
<span class="line"><span class="cl">
</span></span> <span class="k">if</span><span class="p">(</span><span class="n">validacion</span><span class="p">.</span><span class="n">IsFailure</span><span class="p">) </span><span class="c1">// -> Si alguna validación falla no instanciamos la clase</span>
<span class="k">return</span> <span class="n">validacion</span><span class="p">.</span><span class="n">ConvertTo</span><span class="p"><</span><span class="n">Persona</span><span class="p">>(); </span><span class="c1">-> Devolvemos el error de la validación</span>
<span class="line"><span class="cl">
</span></span> <span class="k">return</span> <span class="k">new</span> <span class="n">Persona</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="kt">string</span> <span class="n">nombre</span><span class="p">,</span> <span class="kt">string</span> <span class="n">apellidos</span><span class="p">); </span><span class="c1">// -> todo ha ido bien :)</span>
<span class="p">}</span>
<span class="line"><span class="cl">
</span></span><span class="c1">// Constructor privado. Sólo accesible desde el método anterior</span>
<span class="k">private</span> <span class="n">Persona</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">,</span> <span class="kt">string</span> <span class="n">nombre</span><span class="p">,</span> <span class="kt">string</span> <span class="n">apellidos</span><span class="p">)</span> <span class="p">:</span> <span class="k">base</span><span class="p">(</span><span class="n">id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">Nombre</span> <span class="p">=</span> <span class="n">nombre</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">Apellidos</span> <span class="p">=</span> <span class="n">apellidos</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="kt">string</span> <span class="n">Nombre</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// set privado</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="kt">string</span> <span class="n">Apellidos</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span> <span class="c1">// set privado</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="n">Result</span> <span class="n">CambiarNombre</span><span class="p">(</span><span class="kt">string</span> <span class="n">nombre</span><span class="p">,</span> <span class="kt">string</span> <span class="n">apellidos</span><span class="p">)</span> <span class="c1">// método para modificar las propiedades</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
<span class="kt">var</span> <span class="n">validacion</span> <span class="p">=</span> <span class="n">Result</span><span class="p">.</span><span class="n">Combine</span><span class="p">( </span><span class="c1">// -> Reutilizamos las validaciones utilizadas en el método Crear</span>
<span class="n">ValidarNombre</span><span class="p">(</span><span class="n">nombre</span><span class="p">),</span>
</span></span><span class="line"><span class="cl"> <span class="n">ValidarApellidos</span><span class="p">(</span><span class="n">apellidos</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">if</span><span class="p">(</span><span class="n">validacion</span><span class="p">.</span><span class="n">IsFailure</span><span class="p">)</span>
<span class="k">return</span> <span class="n">validacion</span><span class="p">; </span><span class="c1">// -> Si no cumplimos los requisitos devolvemos el mensaje de error</span>
<span class="line"><span class="cl">
</span></span> <span class="n">Nombre</span> <span class="p">=</span> <span class="n">nombre</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">Apellidos</span> <span class="p">=</span> <span class="n">apellidos</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">Result</span><span class="p">.</span><span class="n">Success</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"> // Métodos de validación -> <strong>deben ser estáticos y públicos</strong></span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="k">static</span> <span class="n">Result</span> <span class="n">ValidarNombre</span><span class="p">(</span><span class="kt">string</span> <span class="n">nombre</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p"> {</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">nombre</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">Result</span><span class="p">.</span><span class="n">Failure</span><span class="p">(</span><span class="s">"Indica el nombre de la persona"</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">retunt</span> <span class="n">Result</span><span class="p">.</span><span class="n">Success</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p"> }</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="k">static</span> <span class="n">Result</span> <span class="n">ValidarApellidos</span><span class="p">(</span><span class="kt">string</span> <span class="n">apellidos</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p"> {</span>
</span></span><span class="line"><span class="cl"> <span class="k">if</span><span class="p">(</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">apellidos</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">Result</span><span class="p">.</span><span class="n">Failure</span><span class="p">(</span><span class="s">"Indica los apellidos de la persona"</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="n">retunt</span> <span class="n">Result</span><span class="p">.</span><span class="n">Success</span><span class="p">();</span>
<span class="p"> }
</span><span class="c1">// ... otros métodos de valiación </span>
<span class="p">}</span></span></span></code></pre>
<p>Ni siquiera en la creación del objeto se pueden establecer propiedades que no cumplan con las reglas del negocio.</p>
<p></p>
<h4>6. Encapsula lógica de negocio predecible</h4>
<p><strong>Los métodos de las entidades encapsulan<a href="/logica-negocio-programacion/" target="_blank"> lógica de negocio</a></strong>.</p>
<p>Recordemos que las entidades representan una situación que se está dando en el mundo real. Debemos colocar dentro de sus métodos la lógica que procese y aplique las reglas de negocio.</p>
<p>Por otro lado, debemos evitar hacer llamadas externas o no predecibles dentro de los métodos. Los métodos deben ser <a href="/efectos-secundarios-idempotencia-programacion/" target="_blank">idempotentes</a>, es decir, que sean predecibles.</p>
<p>Está "prohibido":</p>
<ul>
<li>Obtener datos de una base de datos. Si los necesitas, pásalos por parámetro en el método.</li>
<li>Acceder al sistema de archivos. Equivalente a una llamada a una base de datos.</li>
<li>Utilizar <em>DateTime.Now</em> o <em>DateTime.Today</em>, puesto que sus resultados son impredecibles. Pasa por parámetro la fecha que necesites.</li>
<li>Utilizar la clase <em>Random </em>para generar números aleatorios. Pasa los números necesarios por parámetro.</li>
</ul>
<p>En general, cualquier llamada que no controlemos al 100% no está permitida. ¿Qué nos quedará dentro? Sólo lógica de negocio, aislada y sin dependencias.</p>
<p>¿Qué pasa si quiero validar que no se cree una entidad con alguno de sus valores duplicado? Por ejemplo, no queremos crear personas con el mismo nombre y apellidos.</p>
<p>Este tipo de lógica está en la frontera entre las capas de negocio y de aplicación. Aquí no puedo extenderme más, pero te dejo este <a href="https://enterprisecraftsmanship.com/posts/domain-model-purity-completeness/" target="_blank">post de Vladimir Khorikov</a> titulado <a href="https://enterprisecraftsmanship.com/posts/domain-model-purity-completeness/" target="_blank">DDD trilemma</a> por si quieres ahondar más en el tema.</p>
<p>Respondiendo la pregunta a nivel práctico: </p>
<ul>
<li>Dentro del ámbito de una sola entidad no puedes tener acceso al resto de entidades de la aplicación. Por tanto, la validación de duplicidades <strong>a nivel global</strong> la coloco en la capa de aplicación aunque pertenezca a la capa de negocio. Es una concesión que hago en favor del rendimiento y es excepcional.</li>
</ul>
<p></p>
<h4>7. Conclusiones</h4>
<p>Estas son las características que debe cumplir una entidad:</p>
<ol>
<li>Una entidad debe compararse por referencia y por identidad.</li>
<li>Sus propiedades no tienen <em>set </em>o el <em>set </em>es privado. </li>
<li>El estado de la Entidad (sus propiedades) se modifican a través de sus métodos.</li>
<li>El estado siempre debe ser válido. No se permiten propiedades con valores incorrectos durante su ciclo de vida. </li>
<li>Los métodos de las entidades encapsulan lógica de negocio y son predecibles.</li>
</ol>
<p></p>
https://albertcapdevila.net/logica-negocio-programacion/Qué es la lógica de negocio en programación y cómo distinguirla de la lógica de aplicación y de pantalla<p><a href="logica-negocio-programacion"><img alt="Camino que traza el código" class="img-responsive" src="https://storagequedat.blob.core.windows.net/contenedorblog/e0955397-8687-4831-aa33-e11047b16cc2_caminobosque.jpg" /></a></p>
<p>Foto de <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/@nanichkar?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1653281761851000&usg=AOvVaw1fCy9OrstYSSKMBKL2nMk2" href="https://unsplash.com/@nanichkar?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Anastasiya Romanova</a> en <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/es/s/fotos/forest-way?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1653281761851000&usg=AOvVaw1vd8cVxzmT4Z0aRa_9pbu1" href="https://unsplash.com/es/s/fotos/forest-way?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Unsplash</a></p>
<hr />
<p>A menudo me he encontrado con que en el mundo del desarrollo de software, a cualquier cosa se le llama lógica de negocio o lógica a secas.</p>
<p>En este artículo voy a explicarte qué entiendo por lógica y qué tres tipos básicos podemos distinguir.</p>
<p></p>
2022-05-22T00:00:00Z2022-05-22T03:04:13ZAlbert Capdevilahttp://albertcapdevila.net<p><a href="logica-negocio-programacion"><img alt="Camino que traza el código" class="img-responsive" src="https://storagequedat.blob.core.windows.net/contenedorblog/e0955397-8687-4831-aa33-e11047b16cc2_caminobosque.jpg" /></a></p>
<p>Foto de <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/@nanichkar?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1653281761851000&usg=AOvVaw1fCy9OrstYSSKMBKL2nMk2" href="https://unsplash.com/@nanichkar?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Anastasiya Romanova</a> en <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/es/s/fotos/forest-way?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1653281761851000&usg=AOvVaw1vd8cVxzmT4Z0aRa_9pbu1" href="https://unsplash.com/es/s/fotos/forest-way?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Unsplash</a></p>
<hr />
<p>A menudo me he encontrado con que en el mundo del desarrollo de software, a cualquier cosa se le llama lógica de negocio o lógica a secas.</p>
<p>En este artículo voy a explicarte qué entiendo por lógica y qué tres tipos básicos podemos distinguir.</p>
<p></p>
<style type="text/css">/* Background */ .bg { background-color: #ffffff }
/* PreWrapper */ .chroma { background-color: #ffffff; }
/* Error */ .chroma .err { }
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
/* LineHighlight */ .chroma .hl { background-color: #e5e5e5 }
/* LineNumbersTable */ .chroma .lnt { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* LineNumbers */ .chroma .ln { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* Line */ .chroma .line { display: flex; }
/* Keyword */ .chroma .k { color: #0000ff }
/* KeywordConstant */ .chroma .kc { color: #0000ff }
/* KeywordDeclaration */ .chroma .kd { color: #0000ff }
/* KeywordNamespace */ .chroma .kn { color: #0000ff }
/* KeywordPseudo */ .chroma .kp { color: #0000ff }
/* KeywordReserved */ .chroma .kr { color: #0000ff }
/* KeywordType */ .chroma .kt { color: #2b91af }
/* NameClass */ .chroma .nc { color: #2b91af }
/* LiteralString */ .chroma .s { color: #a31515 }
/* LiteralStringAffix */ .chroma .sa { color: #a31515 }
/* LiteralStringBacktick */ .chroma .sb { color: #a31515 }
/* LiteralStringChar */ .chroma .sc { color: #a31515 }
/* LiteralStringDelimiter */ .chroma .dl { color: #a31515 }
/* LiteralStringDoc */ .chroma .sd { color: #a31515 }
/* LiteralStringDouble */ .chroma .s2 { color: #a31515 }
/* LiteralStringEscape */ .chroma .se { color: #a31515 }
/* LiteralStringHeredoc */ .chroma .sh { color: #a31515 }
/* LiteralStringInterpol */ .chroma .si { color: #a31515 }
/* LiteralStringOther */ .chroma .sx { color: #a31515 }
/* LiteralStringRegex */ .chroma .sr { color: #a31515 }
/* LiteralStringSingle */ .chroma .s1 { color: #a31515 }
/* LiteralStringSymbol */ .chroma .ss { color: #a31515 }
/* OperatorWord */ .chroma .ow { color: #0000ff }
/* Comment */ .chroma .c { color: #008000 }
/* CommentHashbang */ .chroma .ch { color: #008000 }
/* CommentMultiline */ .chroma .cm { color: #008000 }
/* CommentSingle */ .chroma .c1 { color: #008000 }
/* CommentSpecial */ .chroma .cs { color: #008000 }
/* CommentPreproc */ .chroma .cp { color: #0000ff }
/* CommentPreprocFile */ .chroma .cpf { color: #0000ff }
/* GenericEmph */ .chroma .ge { font-style: italic }
/* GenericHeading */ .chroma .gh { font-weight: bold }
/* GenericPrompt */ .chroma .gp { font-weight: bold }
/* GenericStrong */ .chroma .gs { font-weight: bold }
/* GenericSubheading */ .chroma .gu { font-weight: bold }
body { background-color: #ffffff; }
</style>
<p>Conocer la diferencia entre ellos genera un marco conceptual que facilita la comunicación entre los miembros del equipo y además ayuda a la creación de una buena arquitectura.</p>
<h4>1. ¿Qué es la lógica en programación?</h4>
<p>Imagina que el código que escribimos es como un camino. Lo deseable es que ese camino sea recto y no haya bifurcaciones. Cuanto más recto sea el camino, más fácil será seguirlo y en consecuencia, entenderlo.</p>
<p><strong>Cuando el camino se bifurca nos obliga a usar memoria y comprensión</strong>. "<em>Si pasa esto voy por aquí"</em>, pero "<em>si pasa esto otro voy por allá"</em>. Al escoger una bifurcación, memorizamos el lugar por el que nos hemos desviado para, más adelante, volver e investigar qué hay por el otro camino.</p>
<p>Estas desviaciones las implementamos con cláusulas <strong><em>if's</em></strong>.</p>
<p>La lógica en programación es el conjunto de todos estos <em><strong>if's</strong></em>, o decisiones que debe tomar nuestro código en función de los inputs recibidos.</p>
<p>Pero no toda la lógica que escribimos tiene las mismas características. Es importante distinguir entre:</p>
<ul>
<li>Lógica de negocio</li>
<li>Lógica de aplicación</li>
<li>Lógica de pantalla</li>
</ul>
<p></p>
<h4>2. Lógica de negocio</h4>
<p>A grandes rasgos, una aplicación de software trata de representar una situación que se está dando en el mundo real.</p>
<p>Por ejemplo, en lugar de ir al almacén y contabilizar cuánto stock queda de un producto, puedo ir a mi aplicación y consultarlo.</p>
<p>La lógica de negocio es aquella que toma decisiones en función de conceptos reales y que utiliza el vocabulario que un experto en la materia utilizaría en su día a día.</p>
<p>Cada aplicación tendrá una lógica de negocio basada en el modelo que representa. En un CRM, las decisiones que se tomen (bloques if's) estarán relacionadas, por ejemplo, con si un contacto pasa de prospecto a cliente, o si se debe realizar una acción futura con ese cliente.</p>
<p>Una aplicación para almacenes tomará decisiones en función de la recepción/envío de productos.</p>
<p>Este tipo de lógica es la más importante y también la más complicada. </p>
<p>Se suele colocar en la capa más interna de la aplicación, la denominada capa de dominio, y debería estar aislada de cualquier referencia externa como la base de datos, el sistema de archivos o una api de terceros.</p>
<p></p>
<p>Veamos algún ejemplo.</p>
<p>Pongamos que queremos pagar una factura. La lógica de negocio hará lo siguiente:</p>
<ul>
<li>Dada una factura, validará que se cumplan ciertas condiciones, como por ejemplo que no esté ya pagada, y le cambiará su estado a "pagada". Su misión no es contactar con la api del banco y realizar la transferencia. Su misión es, por un lado, representar que mi modelo abstracto de factura está pagado, y por otro, que se cumplan una pre-condiciones antes de realizar el cambio y unas post-condiciones después del cambio.<br />
</li>
</ul>
<p>Con el stock de un almacén quizá sea más claro.</p>
<ul>
<li>Si envío un producto a un cliente, resto stock del producto, pero mi aplicación no está enviando el producto en el camión. La aplicación comprobará si hay stock suficiente antes de enviar el producto.</li>
</ul>
<p><br />
La lógica de negocio son todas estas decisiones, a nivel abstracto, que están pasando en el mundo real. </p>
<p>Ejemplos de lógica de negocio:</p>
<ul>
<li>Calcular el total de una factura incluyendo los impuestos</li>
<li>Inscribir a un alumno en una clase</li>
<li>Planificar un proyecto</li>
<li>Cerrar una incidencia</li>
<li>Calcular los gastos de envío</li>
<li>Ensilar café en un silo</li>
<li>Reservar una habitación </li>
</ul>
<p></p>
<h4>3. Lógica de aplicación, También conocida como lógica de servicios</h4>
<p>Su misión principal es preparar el contexto necesario y coordinar las decisiones que toma la lógica de negocio. Es una lógica de tipo técnico.</p>
<p>Por ejemplo, imagina que usamos una base de datos para persistir el estado de la aplicación. Cualquier decisión que tome nuestra aplicación para guardar o recuperar datos de la base de datos será puramente técnica, no estará relacionada con el modelo conceptual de negocio.</p>
<p>Las decisiones de lógica de aplicación suelen ser sencillas, y el camino que sigue el código ha de ser casi recto, sin bifurcaciones. Suele haber un único camino "feliz" y las bifurcaciones sólo se usan para salir del él porque alguna premisa no se cumple. </p>
<p>Además, la mayoría de bloques con este tipo de lógica tienen la siguiente estructura:</p>
<ol>
<li>Llamada <strong>externa </strong>a la aplicación. Por ejemplo, recuperar registros de la base de datos.</li>
<li>Llamada <strong>interna </strong>a nuestra capa de negocio. Cambia el estado del sistema.</li>
<li>Otra llamada <strong>externa</strong>. Por ejemplo, guardar los cambios que hemos realizado.<br />
</li>
</ol>
<p>En programación funcional, a estos bloques de código se les llama "Impureim sandwich". Cuando lo explico, me tomo la libertad de llamarlos "métodos bocadillo". El concepto "impure/pure/impure sanwich" se refiere a conceptos funcionales teniendo en cuenta que las funciones se pueden dividir entre puras e impuras. <a href="https://blog.ploeh.dk/2020/03/02/impureim-sandwich/" target="_blank">Mark Seemann lo explica en perfectamente en algunos de sus posts</a>. Te lo recomiendo.</p>
<p>Durante años se ha explicado que la lógica de negocio va en los servicios. Esto se basa en el modelo de capas en que el proyecto cliente depende del proyecto servicio y a su vez este depende de un proyecto de datos. Todo queda acoplado a los datos y además estos representan un modelo anémico.</p>
<p>El código resultante de esta arquitectura presenta numerosas limitaciones desde el punto de vista de la mantenibilidad, pero esto es otra historia y aquí no puedo entrar más en detalle por no alargarme.</p>
<p>Lo que sí puedo asegurar es que aplicar la lógica de aplicación usando métodos bocadillo genera código mantenible y fácil de modificar.</p>
<p></p>
<p>Veamos un ejemplo método de la capa Aplicación o Servicios:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">Result</span><span class="p">></span> <span class="n">PagarFacura</span><span class="p">(</span><span class="kt">int</span> <span class="n">facturaId</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kt">var</span> <span class="n">factura</span> <span class="p">=</span> <span class="k">await</span> <span class="m">_d</span><span class="n">atabase</span><span class="p">.</span><span class="n">Facturas</span><span class="p">.</span><span class="n">FindAsync</span><span class="p">(</span><span class="n">facturaId</span><span class="p">);</span> <span class="c1">// Bocadillo - > parte superior</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">factura</span> <span class="k">is</span> <span class="k">null</span><span class="p">)</span> <span class="c1">// lógica de aplicación</span>
</span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">Result</span><span class="p">.</span><span class="n">Failure</span><span class="p">(</span><span class="s">$"Factura con id {facturaId} no encontrada"</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kt">var</span> <span class="n">pago</span> <span class="p">=</span> <span class="n">factura</span><span class="p">.</span><span class="n">Pagar</span><span class="p">();</span> <span class="c1">// llamada a la capa de negocio. Dentro se aplica lógica de negocio</span>
<span class="line"><span class="cl">
</span></span> <span class="k">if</span><span class="p">(</span><span class="n">pago</span><span class="p">.</span><span class="n">IsFailure</span><span class="p">) </span><span class="c1">// lógica de aplicación para salir del camino principal</span>
<span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">Result</span><span class="p">.</span><span class="n">Failure</span><span class="p">(</span><span class="n">pago</span><span class="p">.</span><span class="n">Error</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
<span class="line"><span class="cl">
</span></span> <span class="k">await</span> <span class="m">_d</span><span class="n">atabase</span><span class="p">.</span><span class="n">SaveChangesAsync</span><span class="p">();</span> <span class="c1">// Bocadillo - > parte inferior
</span><span class="k"> return</span> <span class="n">Result</span><span class="p">.</span><span class="n">Success</span><span class="p">();</span>
<span class="p">}</span></span></span></code></pre>
<p></p>
<p>El camino que toma el código es prácticamente recto. Las bifurcaciones solo controlan casos no deseados, como que no se encuentre la factura en la base de datos o que el pago no se realice por el motivo que sea.</p>
<p>Podríamos añadir más lógica de aplicación, como por ejemplo comprobar si el usuario tiene los permisos necesarios para realizar la acción, o también podríamos llamar a una api del banco para realizar la transferencia realmente. </p>
<p>Ejemplos de lógica de aplicación:</p>
<ul>
<li>Recuperar una factura de base de datos y comprobar si existe</li>
<li>Guardar datos en base de datos</li>
<li>Guardar un fichero en una carpeta</li>
<li>Verificar si el usuario tiene permiso para realizar la acción</li>
<li>Guardar la traza de la acción realizada por el usuario</li>
</ul>
<p></p>
<h4>4. Lógica de pantalla</h4>
<p>Es aquella que toma decisiones sobre los elementos que se muestran en la pantalla y las interacciones que realiza el usuario. Por ejemplo, decide si un botón aparece o no aparece en la pantalla. </p>
<p>Imagina que en función del estado de la factura y los permisos del usuario se debe mostrar el botón de pagar. En cualquier otro caso el botón no debe aparecer en la pantalla.</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="c"><!-- un archivo .cshtml cualquiera --></span>
</span></span><span class="line"><span class="cl"> @if (Model.Factura.Estado == EstadoFactura.Pendiente <span class="err">&&</span> User.TienePermiso(Permiso.PagarFacturas))
</span></span><span class="line"><span class="cl"> {
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">button</span> <span class="na">class</span><span class="o">=</span><span class="s">"btn btn-primary"</span><span class="p">></span>Pagar factura<span class="p"></</span><span class="nt">button</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> }
</span></span></code></pre>
<p>La lógica de la pantalla puede llegar a ser muy complicada. Para estos casos conviene generar un modelo único para la pantalla y encapsular las decisiones en propiedades del modelo:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="c"><!-- un archivo .cshtml cualquiera --></span>
</span></span><span class="line"><span class="cl"> @if (Model.MostrarBotonPagar)
</span></span><span class="line"><span class="cl"> {
</span></span><span class="line"><span class="cl"> <span class="p"><</span><span class="nt">button</span> <span class="na">class</span><span class="o">=</span><span class="s">"btn btn-primary"</span><span class="p">></span>Pagar factura<span class="p"></</span><span class="nt">button</span><span class="p">></span>
</span></span><span class="line"><span class="cl"> }
</span></span></code></pre>
<p>Esto es posible si creamos la propiedad MostrarBotonPagar y la inicializamos de la siguiente manera:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="c1">// el archivo .cshtml.cs correspondiente</span>
</span></span><span class="line"><span class="cl"><span class="n">MostrarBotonPagar</span> <span class="p">=</span> <span class="n">Model</span><span class="p">.</span><span class="n">Factura</span><span class="p">.</span><span class="n">Estado</span> <span class="p">==</span> <span class="n">EstadoFactura</span><span class="p">.</span><span class="n">Pendiente</span> <span class="p">&&</span> <span class="n">User</span><span class="p">.</span><span class="n">TienePermiso</span><span class="p">(</span><span class="n">Permiso</span><span class="p">.</span><span class="n">PagarFacturas</span><span class="p">);</span>
</span></span></code></pre>
<p>De un modo u otro, la lógica de pantalla es la que controla qué elementos pueden aparecer en ella, así como las interacciones que realiza el usuario.</p>
<p></p>
<h4>5. Conclusiones</h4>
<p>Saber con qué tipo de lógica estamos tratando nos ayuda a saber dónde colocar bloques de código y a generar una arquitectura mantenible.</p>
<p>La comunicación es uno de los costes más importantes en el desarrollo de software y a menudo no se tiene en cuenta. Establecer estas bases conceptuales me ayuda a comunicarme mejor con el equipo y a evitar interpretaciones personales.</p>
<p></p>
https://albertcapdevila.net/metodo-deshonesto-refactorizacion/Ejemplo de un método deshonesto en c#.net y cómo refactorizarlo<p><a href="/metodo-deshonesto-refactorizacion"><img alt="Haz que tus métodos digan la verdad" class="img-responsive" src="https://storagequedat.blob.core.windows.net/contenedorblog/202ca842-e3c3-458b-99ee-dd8d6199cbf2_tellthetruth.jpg" /></a></p>
<p>Photo by <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/@purzlbaum?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1650198617139000&usg=AOvVaw3kHOyAJJdAeA9B3DUsClKA" href="https://unsplash.com/@purzlbaum?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Claudio Schwarz</a> on <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1650198617139000&usg=AOvVaw31_5D-SJPUBxJK92fJDvoz" href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Unsplash</a></p>
<hr />
<p>Aquí tienes un ejemplo real de un método deshonesto en c# y un par de opciones para mejorarlo. </p>
2022-04-16T00:00:00Z2022-04-16T10:52:06ZAlbert Capdevilahttp://albertcapdevila.net<p><a href="/metodo-deshonesto-refactorizacion"><img alt="Haz que tus métodos digan la verdad" class="img-responsive" src="https://storagequedat.blob.core.windows.net/contenedorblog/202ca842-e3c3-458b-99ee-dd8d6199cbf2_tellthetruth.jpg" /></a></p>
<p>Photo by <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/@purzlbaum?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1650198617139000&usg=AOvVaw3kHOyAJJdAeA9B3DUsClKA" href="https://unsplash.com/@purzlbaum?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Claudio Schwarz</a> on <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1650198617139000&usg=AOvVaw31_5D-SJPUBxJK92fJDvoz" href="https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Unsplash</a></p>
<hr />
<p>Aquí tienes un ejemplo real de un método deshonesto en c# y un par de opciones para mejorarlo. </p>
<p>Imagina que tu aplicación presenta un listado de facturas por pantalla y necesitas poder filtrarlo por zona geográfica.</p>
<p>Suponiendo que trabajas con <em>EntityFramework</em>, podrías generar un código como el siguiente:</p>
<p></p>
<style type="text/css">/* Background */ .bg { background-color: #ffffff }
/* PreWrapper */ .chroma { background-color: #ffffff; }
/* Error */ .chroma .err { }
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; }
/* LineHighlight */ .chroma .hl { background-color: #e5e5e5 }
/* LineNumbersTable */ .chroma .lnt { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* LineNumbers */ .chroma .ln { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* Line */ .chroma .line { display: flex; }
/* Keyword */ .chroma .k { color: #0000ff }
/* KeywordConstant */ .chroma .kc { color: #0000ff }
/* KeywordDeclaration */ .chroma .kd { color: #0000ff }
/* KeywordNamespace */ .chroma .kn { color: #0000ff }
/* KeywordPseudo */ .chroma .kp { color: #0000ff }
/* KeywordReserved */ .chroma .kr { color: #0000ff }
/* KeywordType */ .chroma .kt { color: #2b91af }
/* NameClass */ .chroma .nc { color: #2b91af }
/* LiteralString */ .chroma .s { color: #a31515 }
/* LiteralStringAffix */ .chroma .sa { color: #a31515 }
/* LiteralStringBacktick */ .chroma .sb { color: #a31515 }
/* LiteralStringChar */ .chroma .sc { color: #a31515 }
/* LiteralStringDelimiter */ .chroma .dl { color: #a31515 }
/* LiteralStringDoc */ .chroma .sd { color: #a31515 }
/* LiteralStringDouble */ .chroma .s2 { color: #a31515 }
/* LiteralStringEscape */ .chroma .se { color: #a31515 }
/* LiteralStringHeredoc */ .chroma .sh { color: #a31515 }
/* LiteralStringInterpol */ .chroma .si { color: #a31515 }
/* LiteralStringOther */ .chroma .sx { color: #a31515 }
/* LiteralStringRegex */ .chroma .sr { color: #a31515 }
/* LiteralStringSingle */ .chroma .s1 { color: #a31515 }
/* LiteralStringSymbol */ .chroma .ss { color: #a31515 }
/* OperatorWord */ .chroma .ow { color: #0000ff }
/* Comment */ .chroma .c { color: #008000 }
/* CommentHashbang */ .chroma .ch { color: #008000 }
/* CommentMultiline */ .chroma .cm { color: #008000 }
/* CommentSingle */ .chroma .c1 { color: #008000 }
/* CommentSpecial */ .chroma .cs { color: #008000 }
/* CommentPreproc */ .chroma .cp { color: #0000ff }
/* CommentPreprocFile */ .chroma .cpf { color: #0000ff }
/* GenericEmph */ .chroma .ge { font-style: italic }
/* GenericHeading */ .chroma .gh { font-weight: bold }
/* GenericPrompt */ .chroma .gp { font-weight: bold }
/* GenericStrong */ .chroma .gs { font-weight: bold }
/* GenericSubheading */ .chroma .gu { font-weight: bold }
body { background-color: #ffffff; }
</style>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IActionResult</span><span class="p">></span> <span class="n">OnGetAsync</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kt">var</span> <span class="n">consulta</span> <span class="p">=</span> <span class="m">_d</span><span class="n">b</span><span class="p">.</span><span class="n">Facturas</span><span class="p">.</span><span class="n">AsQueryable</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">if</span><span class="p">(</span><span class="n">ZonaId</span><span class="p">.</span><span class="n">HasValue</span><span class="p">)</span>
<span class="n">consulta</span> <span class="p">=</span> <span class="n">consulta</span><span class="p">.</span><span class="n">Where</span><span class="p">(</span><span class="n">m</span><span class="p">=></span><span class="n">m</span><span class="p">.</span><span class="n">Zona</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> Zona<span class="n">Id</span><span class="p">);</span>
<span class="line"><span class="cl">
</span></span> <span class="n">Facturas</span> <span class="p">=</span> <span class="k">await</span> <span class="n">consulta.</span><span class="n">ToListAsync</span><span class="p">();</span>
<span class="line"><span class="cl">
</span></span> <span class="k">return</span> <span class="n">Page</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
<p></p>
<p>El parámetro <em>ZonaId</em> representa la zona seleccionada en la pantalla. El valor <em>null </em>de <em>ZonaId</em><em> </em>representa el valor "zona" no seleccionada, para cuando el usuario no quiera filtrar por zona.</p>
<p></p>
<p>La primera versión de código ya es válida y podrías entregar la funcionalidad, pero hay un par de puntos mejorables:</p>
<ul>
<li>Hay un <strong><em>If</em></strong> en medio de las cláusulas LINQ que rompe la dinámica funcional antes de conseguir el resultado final y que además obliga a generar una variable "<em>consulta</em>". Sería deseable eliminar ese <strong><em>if.</em></strong><br />
</li>
<li>Por otro lado, podríamos encapsular el filtro <strong><em>Where</em></strong> y darle semántica. Eso ayudaría a entender mejor la intención del código y además a generar una "pieza de código" reutilizable.</li>
</ul>
<p>Para solucionar estos dos puntos lo primero que se me ocurre es crear una método extensor como el siguinete.</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">FiltrosFactras</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="k">static</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">Factura</span><span class="p">></span> <span class="n">DeZona</span><span class="p">(</span><span class="k" style="background-color: transparent; font-size: inherit;">this</span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">IQueryable</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;"><</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">Factura</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">></span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">facturas</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">, </span><span class="kt" style="background-color: transparent; font-size: inherit;">int?</span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">zonaId</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">)</span></span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">zonaId</span><span class="p">.</span><span class="n">HasValue</span> <span class="p">?</span> <span class="c1">// evalua si el parámetro zona es nulo</span>
</span></span><span class="line"><span class="cl"> <span class="n">facturas</span><span class="p">.</span><span class="n">Where</span><span class="p">(</span><span class="n">c</span> <span class="p">=></span> <span class="n">c</span><span class="p">.</span><span class="n">Zona</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">zonaId</span><span class="p">)</span> <span class="p">:</span> <span class="c1">// devuelve las facturas filtradas por zona</span>
</span></span><span class="line"><span class="cl"> <span class="n">facturas</span><span class="p">;</span> <span class="c1">// devuelve todas las facturas</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
<p>Los métodos de extensión son estáticos y utilizan el <strong><em>this</em></strong> delante del primer parámetro. Entre otros, los suelo utilizar para encapsular filtros <em>Where</em> de <em>LINQ.</em></p>
<p></p>
<p>Con nuestro flamante filtro de zona reutilizable podemos volver a reescribir el código inicial de la siguiente manera:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IActionResult</span><span class="p">></span> <span class="n">OnGetAsync</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="n">Facturas</span> <span class="p">=</span> <span class="k">await</span> <span class="m">_d</span><span class="n">b</span><span class="p">.</span><span class="n">Facturas</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">.</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">DeZona</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">(</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">ZonaId</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">)</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">.</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">ToListAsync</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">();</span></span></span><span class="line"><span class="cl"><span class="line"><span class="cl">
</span></span> <span class="k">return</span> <span class="n">Page</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
<p>A simple vista el resultado parece mejor, ni tenemos el <strong><em>if</em></strong> en mitad de la creación de la consulta, ni necesitamos generar una variable "consulta". Visto así, <strong>parece un método correcto, pero no lo es</strong>.</p>
<p></p>
<p>En este caso nos viene bien, pero eso no significa que el método esté bien diseñado. Veámos porqué:</p>
<p>El parámetro que recibe el método <strong><em>DeZona</em></strong> puede ser un <strong><em>int</em></strong> o un <em><strong>null</strong></em>, por tanto el siguiente código sería perfectamente válido:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"> <span class="n">Facturas</span> <span class="p">=</span> <span class="k">await</span> <span class="m">_d</span><span class="n">b</span><span class="p">.</span><span class="n">Facturas</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">.</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">DeZona</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">(</span><span class="k" style="background-color: transparent; font-size: inherit;">null</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">)</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">.</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">ToListAsync</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">();</span></span></span></code></pre>
<p>Y aquí viene el problema. ¿Qué devuelve el método <strong><em>.DeZona(null)</em></strong>? Obviamente, el método lo acabo de crear y sé que si le paso un <strong><em>null </em></strong>no se va a ejecutar la parte del <strong><em>Where</em></strong>. </p>
<p>Pero imagina ponerte en la piel de un compañero que no ha creado el método. ¿Serías capaz de deducir qué hace el método sin necesidad de navegar a su implementación?</p>
<p>Podría ser que si paso un <strong><em>null </em></strong>devolviese todas las facturas cuya zona sea nula, o podría simplemente no devolver resultados porque no se permiten zonas nulas. Desde fuera no lo sé.</p>
<p>Aquí tienes otra versión del método con la misma firma pero diferente comportamiento:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"> <span class="k">public</span> <span class="k">static</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">Factura</span><span class="p">></span> <span class="n">DeZona</span><span class="p">(</span><span class="k" style="background-color: transparent; font-size: inherit;">this</span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">IQueryable</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;"><</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">Factura</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">></span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">facturas</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">, </span><span class="kt" style="background-color: transparent; font-size: inherit;">int?</span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">zonaId</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">)</span></span></span><span class="line"><span class="cl"> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">zonaId</span><span class="p">.</span><span class="n">HasValue</span> <span class="p">?</span>
</span></span><span class="line"><span class="cl"> <span class="n">facturas</span><span class="p">.</span><span class="n">Where</span><span class="p">(</span><span class="n">c</span> <span class="p">=></span> <span class="n">c</span><span class="p">.</span><span class="n">Zona</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">zonaId</span><span class="p">)</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl"> <span class="n">facturas</span><span class="p">.</span><span class="n">Where</span><span class="p">(</span><span class="n">c</span> <span class="p">=></span> <span class="n">c</span><span class="p">.</span><span class="n">Zona</span> <span class="p">==</span> <span class="k">null</span><span class="p">);</span> <span class="c1">// devuelve las facturas cuya zona es nula</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span></code></pre>
<p>Este caso representa un ejemplo de "método deshonesto", que te obliga a ir a la implementación para averiguar su comportamiento; y conviene minimizar su uso.</p>
<p>Una versión correcta del filtro es la siguiente:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"> <span class="k">public</span> <span class="k">static</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">Factura</span><span class="p">></span> <span class="n">DeZona</span><span class="p">(</span><span class="k">this</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">Factura</span><span class="p">></span> <span class="n">facturas</span><span class="p">,</span> <span class="kt">int</span> <span class="n">zonaId </span><span class="c1">/* zonaId no es nullable */</span><span class="p">) </span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">facturas</span><span class="p">.</span><span class="n">Where</span><span class="p">(</span><span class="n">c</span> <span class="p">=></span> <span class="n">c</span><span class="p">.</span><span class="n">Zona</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">zonaId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span></code></pre>
<p>Con esta firma ya no hay duda de qué filtro va a aplicar.</p>
<p></p>
<p>Sin embargo, esta función nos vuelve a colocar casi como al principio, con un <strong><em>If</em></strong> en medio de las cáusulas de LINQ:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p"><</span><span class="n">IActionResult</span><span class="p">></span> <span class="n">OnGetAsync</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="kt">var</span> <span class="n">consulta</span> <span class="p">=</span> <span class="m">_d</span><span class="n">b</span><span class="p">.</span><span class="n">Facturas</span><span class="p">.</span><span class="n">AsQueryable</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">if</span><span class="p">(</span><span class="n">ZonaId</span><span class="p">.</span><span class="n">HasValue</span><span class="p">)</span>
<span class="n">consulta</span> <span class="p">=</span> <span class="n">consulta</span><span class="p">.</span><span class="n">DeZona</span><span class="p">(</span><span class="n">ZonaId</span><span class="p">); </span><span class="c1">// sólo hemos aplicado este cambio</span>
<span class="line"><span class="cl">
</span></span> <span class="n">Facturas</span> <span class="p">=</span> <span class="k">await</span> <span class="n">consulta</span><span class="p">.</span><span class="n">ToListAsync</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">Page</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre>
<p>Es responsabilidad del "código cliente" (aquel que está usando el método) aplicar los filtros en función de los parámetros seleccionados por el usuario. Por tanto, aunque no nos guste, esta versión es más correcta.</p>
<p></p>
<p>El tema del <strong><em>if</em></strong> es otra historia y hay que atacarlo de forma individual. Para solventarlo se me ocurren dos opciones:</p>
<ul>
<li><strong>Opción 1</strong>: crear otro método de extensión con un nombre diferente que describa exactamente lo que va ha hacer.
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="k">public</span> <span class="k">static</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">Factura</span><span class="p">></span> <span class="n">DeZonaSiNoEsNula</span><span class="p">(</span><span class="k" style="background-color: transparent; font-size: inherit;">this</span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">IQueryable</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;"><</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">Factura</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">></span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">facturas</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">, </span><span class="kt" style="background-color: transparent; font-size: inherit;">int?</span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">zonaId</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">)</span>
<span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">zonaId</span><span class="p">.</span><span class="n">HasValue</span> <span class="p">?</span> </span></span><span class="line"><span class="cl"> <span class="n">facturas</span><span class="p">.</span><span class="n">Where</span><span class="p">(</span><span class="n">c</span> <span class="p">=></span> <span class="n">c</span><span class="p">.</span><span class="n">Zona</span><span class="p">.</span><span class="n">Id</span> <span class="p">==</span> <span class="n">zonaId</span><span class="p">)</span> <span class="p">:</span> </span></span><span class="line"><span class="cl"> <span class="n">facturas</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span></span></span><span class="line"><span class="cl">
</span></span></code>
</pre>
</li>
<li><strong>Opción 2</strong>: crear un método de extensión para evaluar la parte del <strong><em>if </em></strong>y que permitiese algo así:
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="n">Facturas</span> <span class="p">=</span> <span class="k">await</span> <span class="m">_d</span><span class="n">b</span><span class="p">.</span><span class="n">Facturas</span> </span></span><span class="line"><span class="cl"> <span class="p">.</span><span class="n">If</span><span class="p">(</span><span class="n">ZonaId</span><span class="p">.</span><span class="n">HasValue</span><span class="p">)</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">.</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">Then</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">(</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">f</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">=></span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">f</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">.</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">DeZona</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">(</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">ZonaId</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">.</span><span class="n" style="background-color: transparent; color: inherit; font-size: inherit;">Value</span><span class="p" style="background-color: transparent; color: inherit; font-size: inherit;">))</span><span style="background-color: transparent; color: inherit; font-size: inherit;"> </span></span></span><span class="line"><span class="cl"> <span class="p">.</span><span class="n">ToListAsync</span><span class="p">();</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"><span class="c1">// Esta segunda opción necesitaría apoyarse en métodos If y Then como éstos:</span> </span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="k">static</span> <span class="p">(</span><span class="kt">bool</span><span class="p">,</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">T</span><span class="p">>)</span> <span class="n">If</span><span class="p"><</span><span class="n">T</span><span class="p">>(</span><span class="k">this</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">T</span><span class="p">></span> <span class="n">consulta</span><span class="p">,</span><span class="kt">bool</span> <span class="n">condicion</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="p">(</span><span class="n">condicion</span><span class="p">,</span> <span class="n">consulta</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> </span></span><span class="line"><span class="cl"> <span class="k">public</span> <span class="k">static</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">T</span><span class="p">></span> <span class="n">Then</span><span class="p"><</span><span class="n">T</span><span class="p">>(</span><span class="k">this</span> <span class="p">(</span><span class="kt">bool</span><span class="p">,</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">T</span><span class="p">>)</span> <span class="n">tupla</span><span class="p">,</span> <span class="n">Func</span><span class="p"><</span><span class="n">IQueryable</span><span class="p"><</span><span class="n">T</span><span class="p">>,</span><span class="n">IQueryable</span><span class="p"><</span><span class="n">T</span><span class="p">>></span> <span class="n">funcion</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">if</span> <span class="p">(</span><span class="n">tupla</span><span class="p">.</span><span class="n">Item1</span><span class="p">)</span> </span></span><span class="line"><span class="cl"> <span class="p">{</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">funcion</span><span class="p">(</span><span class="n">tupla</span><span class="p">.</span><span class="n">Item2</span><span class="p">);</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span> </span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">tupla</span><span class="p">.</span><span class="n">Item2</span><span class="p">;</span> </span></span><span class="line"><span class="cl"> <span class="p">}</span></span></span></code></pre>
</li>
</ul>
<p>Ambas soluciones son correctas e incluso se podrían combinar. Por ejemplo así:</p>
<pre class="chroma" tabindex="0">
<code><span class="line"><span class="cl"><span class="c1">// Opción 1 con métetodo con un nombre diferente</span>
<span class="k">public</span> <span class="k">static</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">Factura</span><span class="p">></span> <span class="n">DeZonaSiNoEsNula</span><span class="p">(</span><span class="k">this</span> <span class="n">IQueryable</span><span class="p"><</span><span class="n">Factura</span><span class="p">></span> <span class="n">facturas</span><span class="p">,</span> <span class="kt">int?</span> <span class="n">zonaId</span><span class="p">)</span>
<span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">return</span> <span class="n">facturas</span>
<span class="p">.</span><span class="n">If</span><span class="p">(</span><span class="n">zonaId</span><span class="p">.</span><span class="n">HasValue</span><span class="p">) </span><span class="c1">// Opción 2 que usa métodos de extensión </span>
<span class="p">.</span><span class="n">Then</span><span class="p">(</span><span class="n">facturas</span><span class="p">.</span><span class="n">DeZona</span><span class="p">(</span><span class="n">zonaId</span><span class="p">.</span><span class="n">Value</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"> <span class="p">}</span></span></span></code></pre>
<p></p>
<p>Es responsabilidad del programador aplicar en cada caso particular las herramientas adecuadas, a veces simplemente es cuestión de gustos.</p>
<p></p>
<p>.</p>
https://albertcapdevila.net/efectos-secundarios-idempotencia-programacion/Efectos secundarios e idempotencia en programación<p><a href="/efectos-secundarios-idempotencia-programacion"><img alt="Efectos secundarios y idempotencia en programación" class="img-responsive" src="https://storagequedat.blob.core.windows.net/contenedorblog/fb7d1fbc-8264-4454-9fdf-263767617583_efectossecundarios.jpg" /></a></p>
<p>Fotografía de <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/@nathandias?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1629817914921000&usg=AFQjCNGKat9r4Zwkj15DEhquPOEkezpNPw" href="https://unsplash.com/@nathandias?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Nathan Dias</a> en <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/s/photos/drug-box?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1629817914921000&usg=AFQjCNE2plwN3iosc_rSf6pfJL16WsUGyA" href="https://unsplash.com/s/photos/drug-box?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Unsplash</a></p>
<hr />
<p>Para comprender patrones de diseño básicos como <em>Command Query Separation CQS</em> o la base teórica detrás de la programación funcional es necesario saber qué son los efectos secundarios en programación y cuándo se producen. En inglés se conocen como <em><strong>side effects</strong></em>.</p>
<p>En el post anterior definí lo que era el <a href="/estado-programacion-aplicacion" target="_blank">estado de la aplicación</a>, te recomiendo tenerlo presente para comprender mejor este texto. </p>
2021-08-23T00:00:00Z2021-08-23T13:25:21ZAlbert Capdevilahttp://albertcapdevila.net<p><a href="/efectos-secundarios-idempotencia-programacion"><img alt="Efectos secundarios y idempotencia en programación" class="img-responsive" src="https://storagequedat.blob.core.windows.net/contenedorblog/fb7d1fbc-8264-4454-9fdf-263767617583_efectossecundarios.jpg" /></a></p>
<p>Fotografía de <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/@nathandias?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1629817914921000&usg=AFQjCNGKat9r4Zwkj15DEhquPOEkezpNPw" href="https://unsplash.com/@nathandias?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Nathan Dias</a> en <a data-saferedirecturl="https://www.google.com/url?q=https://unsplash.com/s/photos/drug-box?utm_source%3Dunsplash%26utm_medium%3Dreferral%26utm_content%3DcreditCopyText&source=gmail&ust=1629817914921000&usg=AFQjCNE2plwN3iosc_rSf6pfJL16WsUGyA" href="https://unsplash.com/s/photos/drug-box?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText" target="_blank">Unsplash</a></p>
<hr />
<p>Para comprender patrones de diseño básicos como <em>Command Query Separation CQS</em> o la base teórica detrás de la programación funcional es necesario saber qué son los efectos secundarios en programación y cuándo se producen. En inglés se conocen como <em><strong>side effects</strong></em>.</p>
<p>En el post anterior definí lo que era el <a href="/estado-programacion-aplicacion" target="_blank">estado de la aplicación</a>, te recomiendo tenerlo presente para comprender mejor este texto. </p>
<style type="text/css">/* Background */ .chroma { background-color: #fafafa }
/* Error */ .chroma .err {
background-color: #fdd;
}
/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #e5e5e5 }
/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f }
/* Keyword */ .chroma .k { color: #0000ff }
/* KeywordConstant */ .chroma .kc { color: #0000ff }
/* KeywordDeclaration */ .chroma .kd { color: #0000ff }
/* KeywordNamespace */ .chroma .kn { color: #0000ff }
/* KeywordPseudo */ .chroma .kp { color: #0000ff }
/* KeywordReserved */ .chroma .kr { color: #0000ff }
/* KeywordType */ .chroma .kt { color: #2b91af }
/* NameClass */ .chroma .nc { color: #2b91af }
/* LiteralString */ .chroma .s { color: #a31515 }
/* LiteralStringAffix */ .chroma .sa { color: #a31515 }
/* LiteralStringBacktick */ .chroma .sb { color: #a31515 }
/* LiteralStringChar */ .chroma .sc { color: #a31515 }
/* LiteralStringDelimiter */ .chroma .dl { color: #a31515 }
/* LiteralStringDoc */ .chroma .sd { color: #a31515 }
/* LiteralStringDouble */ .chroma .s2 { color: #a31515 }
/* LiteralStringEscape */ .chroma .se { color: #a31515 }
/* LiteralStringHeredoc */ .chroma .sh { color: #a31515 }
/* LiteralStringInterpol */ .chroma .si { color: #a31515 }
/* LiteralStringOther */ .chroma .sx { color: #a31515 }
/* LiteralStringRegex */ .chroma .sr { color: #a31515 }
/* LiteralStringSingle */ .chroma .s1 { color: #a31515 }
/* LiteralStringSymbol */ .chroma .ss { color: #a31515 }
/* OperatorWord */ .chroma .ow { color: #0000ff }
/* Comment */ .chroma .c { color: #008000 }
/* CommentHashbang */ .chroma .ch { color: #008000 }
/* CommentMultiline */ .chroma .cm { color: #008000 }
/* CommentSingle */ .chroma .c1 { color: #008000 }
/* CommentSpecial */ .chroma .cs { color: #008000 }
/* CommentPreproc */ .chroma .cp { color: #0000ff }
/* CommentPreprocFile */ .chroma .cpf { color: #0000ff }
/* GenericEmph */ .chroma .ge { font-style: italic }
/* GenericHeading */ .chroma .gh { font-weight: bold }
/* GenericPrompt */ .chroma .gp { font-weight: bold }
/* GenericStrong */ .chroma .gs { font-weight: bold }
/* GenericSubheading */ .chroma .gu { font-weight: bold }
</style>
<h4>1. Qué es un efecto secundario</h4>
<blockquote>
<p>Un método produce un efecto secundario si modifica el <a href="https://albertcapdevila.net/estado-programacion-aplicacion" target="_blank"><em>estado de la aplicación</em></a>.</p>
</blockquote>
<p>Se llama efecto secundario porque produce una salida extra observable además de devolver un valor, el efecto principal, al invocador del método.</p>
<p>Vamos a ver tres situaciones donde pueden ocurrir dichos efectos secundarios:</p>
<ol>
<li>Modificar el <strong>estado de una clase</strong>, es decir, modificar una variable privada o una propiedad de la misma clase. Las variables internas del método no cuentan.<br />
Por ejemplo, en el método "Avanzar" modificamos el valor de "_posicion":<br />
<pre class="chroma">
<span class="k">public</span> <span class="k">class</span> <span class="nc">Vehiculo</span>
<span class="p">{</span>
<span class="k"> private</span> <span class="kt">int</span> <span class="m">_</span><span class="n">posicion</span><span class="p">;</span>
<span class="k">public</span> <span class="n">Vehiculo</span><span class="p">(</span><span class="kt">int</span> <span class="n">posicion</span><span class="p">)</span>
<span class="p"> {</span>
<span class="m"> _</span><span class="n">posicion</span> <span class="p">=</span> <span class="n">posicion</span><span class="p">;</span>
<span class="p"> }</span>
<span class="k"> public</span> <span class="k">void</span> <span class="n">Avanzar</span><span class="p">(</span><span class="kt">int</span> <span class="n">metros</span><span class="p">)</span>
<span class="p"> {</span>
<span class="m"> _</span><span class="n">posicion</span> <span class="p">=</span> <span class="m">_</span><span class="n">posicion</span> <span class="p">+</span> <span class="n">metros</span><span class="p">;</span> <span class="c1">// modifica el estado de una clase
</span> <span class="p">}</span>
<span class="p">}</span></pre>
</li>
<li>Modificar el <strong>estado global</strong>, es decir, modificar una variable o propiedad estática (<em>static</em>).<br />
Por ejemplo, en una clase modificamos la propiedad global "RutaCarpetaInformes":<br />
<pre class="chroma">
<span class="k">public</span> <span class="k">static</span> <span class="k">class</span> <span class="nc">ClaseConVariablesGlobales</span>
<span class="p">{</span>
<span class="k"> public</span> <span class="k">static</span> <span class="kt">string</span> <span class="n">RutaCarpetaInformes</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">class</span> <span class="nc">ClaseParaConfigurar</span>
<span class="p">{</span>
<span class="k"> public</span> <span class="k">void</span> <span class="n">ConfigurarCarpetaInforme</span><span class="p">(</span><span class="kt">string</span> <span class="n">ruta</span><span class="p">)</span>
<span class="p"> {</span>
<span class="n"> </span><span class="nc">ClaseConVariablesGlobales</span><span class="p">.</span><span class="n">RutaCarpetaInformes</span> <span class="p">=</span> <span class="n">ruta</span><span class="p">;</span> <span class="c1">// modifica el estado global
</span> <span class="p">}</span>
<span class="p">}</span></pre>
</li>
<li>Modificar el <strong>estado externo</strong>, por ejemplo, guardar datos en disco.<br />
Por ejemplo, al escribir un texto en un fichero:<br />
<pre class="chroma">
<span class="k">class</span> <span class="nc">ClaseParaEscribirEnArchivo</span>
<span class="p">{</span>
<span class="k"> public</span> <span class="k">void</span> <span class="n">EscribirTextoEnArchivo</span><span class="p">(</span><span class="kt">string</span> <span class="n">texto</span><span class="p">)</span>
<span class="p"> {</span>
<span class="n"> File</span><span class="p">.</span><span class="n">WriteAllText</span><span class="p">(</span><span class="s">"Archivo.txt"</span><span class="p">,</span> <span class="n">texto</span><span class="p">); </span><span class="c1">// modifica el estado externo</span>
<span class="p"> }</span>
<span class="p">}</span></pre>
</li>
</ol>
<h4></h4>
<h4>2. Lanzar una excepción no produce un efecto secundario</h4>
<p>Aunque pueda parecerlo, las excepciones no generan efectos secundarios.</p>
<p>Primero recordamos qué dos tipos de excepciones podemos obtener:</p>
<ul>
<li>Las excepciones <strong>deterministas </strong>son aquellas que se producen siempre que reciben ciertos valores. Por ejemplo, si a un método que realiza una división le pasamos un 0 cómo parámetro de entrada, siempre va a lanzar la misma excepción (<em>Divide<wbr />ByZero<wbr />Exception</em>).<br />
</li>
<li>Las excepciones <strong>no deterministas </strong>son aquellas que ocurren de manera excepcional y que no siempre se producen aunque los <em>inputs </em>sean los mismos. Por ejemplo, un método que requiera realizar muchos cálculos se podría quedar sin memoria antes de conseguir devolver el resultado final (<em>Out<wbr />OfMemory<wbr />Exception</em>), pero ejecutado en una máquina más potente no lanzaría la excepción.</li>
</ul>
<p>Ambos casos representan el flujo por el cual se ha ido ejecutando el código, pero en ninguno de ellos se modifica un valor. Solo si nosotros modificamos una variable dentro de un bloque "<em>Catch</em>" sí que se produciría un efecto secundario. Pero eso ya no vendría dado por la excepción, sino por nuestra intervención.</p>
<p></p>
<blockquote>
<p>Por tanto, <strong>las excepciones no producen efectos secundarios</strong> porque no modifican el estado de la aplicación. </p>
</blockquote>
<p></p>
<p></p>
<h4>3. Capturar una excepción no produce un efecto secundario</h4>
<p>Otro caso que genera dudas es capturar la excepción con un <em><strong>try catch</strong></em> y tomar alguna decisión en base a ella.</p>
<p>Imagina el siguiente código:</p>
<pre class="chroma">
<span class="k">public</span> <span class="kt">decimal</span> <span class="n">RealizarCalculo</span><span class="p">(</span><span class="kt">int</span> <span class="n">input</span><span class="p">)</span>
<span class="p">{</span>
<span class="k"> try</span>
<span class="p"> {</span>
<span class="c1"><font color="#333333"> </font>// Código del cálculo
// ...
</span> <span class="k">return</span> <span class="n">resultado</span><span class="p">;</span>
<span class="p"> }</span>
<span class="k"> catch</span> <span class="p">(</span><span class="n">OutOfMemoryException</span><span class="p">)</span>
<span class="p"> {</span>
<span class="c1"> //Te has quedado sin memoria: vuelve a intentarlo;
</span> <span class="k">return</span> <span class="p">-</span><span class="m">1</span><span class="p">;</span>
<span class="p"> }</span>
<span class="k"> catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">e</span><span class="p">)</span>
<span class="p"> {</span>
<span class="n"> Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="k"> throw</span><span class="p">;</span>
<span class="p"> }</span>
<span class="p">}</span></pre>
<p>Aunque intuitivamente no lo parezca, este ejemplo tampoco produce un efecto secundario. Lo que realmente está pasando es que no podemos predecir el resultado, no siempre va a ser el mismo, pero no modifica ni propiedades ni campos, no cambia una variable global y tampoco escribe en disco. </p>
<p>Semánticamente hablando es equivalente a una función que devuelve valores aleatorios:</p>
<pre class="chroma">
<span class="k">public</span> <span class="kt">int</span> <span class="n">NumeroAleatorio</span><span class="p">()</span>
<span class="p"> {</span>
<span class="n"> Random</span> <span class="n">random</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Random</span><span class="p">();</span>
<span class="k"> return</span> <span class="n">random</span><span class="p">.</span><span class="n">Next</span><span class="p">();</span>
<span class="p"> }</span></pre>
<p>Este método no es puro (definición que veremos más adelante) pero no porque no produzca un efecto secundario, sino por inclumplir otra precondición de la definición de función pura: no es predecible.</p>
<p></p>
<h4>4. Trabajar con DateTime.Now / DateTime.Today no produce efectos secundarios</h4>
<p>Otro caso que causa mucha confusión es si el uso de <em>DateTime.Now / DateTime.Today</em> produce efectos secundarios. La respuesta es no, pero es imposible predecir qué valor va a devolver porque depende de factores externos a dicha función. Está ligado a un valor físico externo como es el reloj del sistema operativo.</p>
<p>Su uso es equivalente a una función que devuelva valores aleatorios.</p>
<p></p>
<p></p>
<h4>5. Qué es la idempotencia en programación</h4>
<blockquote>
<p>El término <b>idempotente</b> se usa para describir una operación que produce los mismos resultados ya se ejecute una o varias veces.</p>
</blockquote>
<p>Por ejemplo un método que elimina un elemento de un conjunto siempre produce el mismo resultado. Puedes llamar al mismo método tantas veces como quieras que no va a ocurrir nada nuevo.</p>
<pre class="chroma">
<span class="n">EliminarUsuario</span><span class="p">(</span><span class="kt">int</span> <span class="n">usuarioId</span><span class="p">);</span>
<span class="n">EliminarUsuario</span><span class="p">(</span><span class="kt">int</span> <span class="n">usuarioId</span><span class="p">);</span>
<span class="n">EliminarUsuario</span><span class="p">(</span><span class="kt">int</span> <span class="n">usuarioId</span><span class="p">);</span>
</pre>
<p><strong>Que un método sea idempotente no significa que no genere efectos secundarios.</strong></p>
<p>En este ejemplo, la primera vez que se llama al método se produce el <em>efecto secundario</em> de eliminar al usuario, pero las siguientes veces que se llame, el usuario ya estará eliminado y el sistema ya no se verá afectado.</p>
<p>Como no se puede saber si el usuario existe de antemano debemos considerar que sí se produce un efecto secundario.</p>
<p></p>
<p></p>
<h4>6. No confundir efecto secundario con función pura</h4>
<p>A menudo se confunde entre la definición de <em>efecto secundario</em> y <em>función pura</em>. </p>
<p><strong>Una función pura</strong> es aquella que cumple dos características muy concretas:</p>
<ol>
<li><strong>Es idempotente</strong> (mismo resultado si se ejecuta una o varias veces)</li>
<li><strong>No produce efectos secundarios</strong></li>
</ol>
<p>Por tanto, todas las funciones puras están libres de efectos secundarios, pero no todas las funciones libres de efectos secundarios son funciones puras. </p>
<h4></h4>
<h4>7. EJEMPLOS</h4>
<p>Vamos a ver un mismo ejemplo con pequeñas variaciones que producen comportamientos diferentes:</p>
<pre class="chroma">
<span class="c1">// Método sin efectos secundarios pero no predecible</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">CalcularEdad</span><span class="p">(</span><span class="n">DateTime</span> <span class="n">fechaNacimiento</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt"> var</span> <span class="n">hoy</span> <span class="p">=</span> <span class="n">DateTime</span><span class="p">.</span><span class="n">Today</span><span class="p">; </span><span class="c1">// el uso del DateTime.Today genera códio no predecible</span>
<span class="kt"> var</span> <span class="n">edad</span> <span class="p">=</span> <span class="n">hoy</span><span class="p">.</span><span class="n">Year</span> <span class="p">-</span> <span class="n">fechaNacimiento</span><span class="p">.</span><span class="n">Year</span><span class="p">;</span>
<span class="k"> if</span> <span class="p">(</span><span class="n">fechaNacimiento</span><span class="p">.</span><span class="n">DayOfYear</span> <span class="p">></span> <span class="n">hoy</span><span class="p">.</span><span class="n">DayOfYear</span><span class="p">)</span>
<span class="p"> {</span>
<span class="n"> edad</span><span class="p">--;</span>
<span class="p"> }</span>
<span class="k"> return</span> <span class="n">edad</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Pasando el día por parámetro convertimos la función anterior en una función pura.
// Sin efectos secundarios e idempotente. </span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">CalcularEdadEnDia</span><span class="p">(</span><span class="n">DateTime</span> <span class="n">fechaNacimiento</span><span class="p">,</span> <span class="n">DateTime</span> <span class="n">dia</span><span class="p">) </span>
<span class="p">{</span>
<span class="kt"> var</span> <span class="n">edad</span> <span class="p">=</span> <span class="n">dia</span><span class="p">.</span><span class="n">Year</span> <span class="p">-</span> <span class="n">fechaNacimiento</span><span class="p">.</span><span class="n">Year</span><span class="p">;</span>
<span class="k"> if</span> <span class="p">(</span><span class="n">fechaNacimiento</span><span class="p">.</span><span class="n">DayOfYear</span> <span class="p">></span> <span class="n">dia</span><span class="p">.</span><span class="n">DayOfYear</span><span class="p">)</span>
<span class="p"> {</span>
<span class="n"> edad</span><span class="p">--;</span>
<span class="p"> }</span>
<span class="k"> return</span> <span class="n">edad</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// Método idempotente pero con efectos secundarios</span>
<span class="k">private</span> <span class="kt">int</span> <span class="m">_</span><span class="n">edad</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">int</span> <span class="n">CalcularEdadEnDiaYGuardar</span><span class="p">(</span><span class="n">DateTime</span> <span class="n">fechaNacimiento</span><span class="p">,</span> <span class="n">DateTime</span> <span class="n">dia</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt"> var</span> <span class="n">edad</span> <span class="p">=</span> <span class="n">dia</span><span class="p">.</span><span class="n">Year</span> <span class="p">-</span> <span class="n">fechaNacimiento</span><span class="p">.</span><span class="n">Year</span><span class="p">;</span>
<span class="k"> if</span> <span class="p">(</span><span class="n">fechaNacimiento</span><span class="p">.</span><span class="n">DayOfYear</span> <span class="p">></span> <span class="n">dia</span><span class="p">.</span><span class="n">DayOfYear</span><span class="p">)</span>
<span class="p"> {</span>
<span class="n"> edad</span><span class="p">--;</span>
<span class="p"> }
</span>
<span class="m"> _</span><span class="n">edad</span> <span class="p">=</span> <span class="n">edad</span><span class="p">; </span><span class="c1">// cambio del estado de la aplicación</span>
<span class="k"> return</span> <span class="n">edad</span><span class="p">;</span>
<span class="p">}</span></pre>
<p></p>
<p>Espero que este artículo te haya provocado un efecto secundario útil. :)</p>
<h3 id="nondeterminism-and-side-effects"></h3>
<p></p>