tag:blogger.com,1999:blog-61684655706632150532024-03-05T03:46:11.992-08:00ALT+F13Unknownnoreply@blogger.comBlogger136125tag:blogger.com,1999:blog-6168465570663215053.post-18330084030425925942023-04-13T23:53:00.002-07:002023-04-13T23:53:55.751-07:00Sincronicidad forzada.<p> El otro día leí <a href="https://www.prequel.co/blog/sql-maxis-why-we-ditched-rabbitmq-and-replaced-it-with-a-postgres-queue" target="_blank">esta entrada de un blog</a>, que estaba en <a href="https://news.ycombinator.com/" target="_blank">Hacker News</a>, sobre como habían desechado RabbitMQ y se habían quedado usando una tabla de Postgres simulando una cola FIFO con la ayuda de los bloqueos y aislamiento que provee el motor de datos relacionales.</p><p> No la voy a explicar aquí así que, si quieres contexto para el resto de la entrada, te animo a que la leas primero antes de continuar.</p><span><a name='more'></a></span><p></p><p>Como has podido leer, se está ejecutando un proceso de larga duración (horas) y se esta esperando a que este proceso acabe para mandar un ACK a RabbitMQ. Mientras; el sistema de prefetch del consumidor de mensajes del bus está bloqueando el siguiente mensaje que termina dando una especie de timeout.</p><p>Se puede apreciar que están forzando la sincronicidad cuando lo que deberían hacer es abrazar la asincronía. Están equivocando el contexto y significado de los mensajes del proceso.<br /></p><p>Pongamos en contexto lo que debería significar cada mensaje y ACK del bus para aceptar la asincronía:</p><p><i>JobMessage </i>- Este es el mensaje inicial del proceso; se crea y se pone en la cola del Bus. Refleja en persistencia la creación de un nuevo Job pendiente de procesar.<br /></p><p><i>ACKJobMessaje </i>- Esto se debe enviar, no cuando el Job ha terminado correctamente, si no cuando JobMessage ha sido <b>validado y procesado</b> correctamente por el consumidor y se ha puesto una maquina a trabajar en el Job sin que haya saltado ningún fallo inmediato. También refleja el cambio en persistencia de poner el estado del Job en "Running".<br /></p><p> Y para abrazar la asincronía hay que introducir un nuevo mensaje: <br /></p><p><i>JobFinished </i>- Este mensaje es enviado al Bus cuando el trabajo de larga duración ha terminado. Marcando efectivamente en persistencia el estado final del Job.</p><p></p><p>Este patrón me suena... claro! esto es una <a href="https://docs.particular.net/nservicebus/sagas/" target="_blank">Saga</a>. Un proceso de larga duración que necesita de persistencia para mantener sus estados intermedios.</p><p><br /></p><p>Y ya lo tenemos, un simple cambio de lo que significa hacer ACK a un mensaje del Bus consigue que todas las piezas del puzle encajen a la perfección sin tener que sobreexplotar el motor relacional.<br /></p>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-21592177183117513502023-04-12T23:45:00.009-07:002023-04-13T01:29:10.778-07:00Programar con ChatGPT y un sacacorchos<p style="text-align: justify;">Pues la IA sabe las respuestas pero hay que sacarlas con sacacorchos. Es una pena porque lo bonito de esto sería que cuando te diese una respuesta "pro", si hay algo que no entiendes o una característica que no conocías, le pudieses preguntar y así aprendes.</p><p style="text-align: justify;"></p><p style="text-align: justify;">Voy a poner una conversación muy sencilla que he tenido con esta IA en la que se ve que le tengo que ir pidiendo las cosas poco a poco; eso significa que el usuario tiene que conocer las características, por lo que si el usuario no sabe, ChatGPT tampoco...</p><div style="text-align: justify;"><span><a name='more'></a></span></div><p style="text-align: justify;"><br /></p><div class="flex flex-grow flex-col gap-3" style="text-align: justify;"><div class="min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap"><b>YO</b> - puedes crearme un esqueleto de un webscraper en Node.js?</div></div><div class="group w-full text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]" style="text-align: justify;"><div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto"><div class="w-[30px] flex flex-col relative items-end"><div class="relative h-[30px] w-[30px] p-1 rounded-sm text-white flex items-center justify-center" style="background-color: #10a37f;"><svg class="h-6 w-6" fill="none" height="41" stroke-width="1.5" viewbox="0 0 41 41" width="41" xmlns="http://www.w3.org/2000/svg"></svg></div></div></div></div><div class="flex flex-grow flex-col gap-3" style="text-align: justify;"><div class="min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap"><div class="markdown prose w-full break-words dark:prose-invert light"><p><b>ChatGPT</b> - ¡Claro que sí! Aquí te dejo un esqueleto básico de un web scraper en Node.js utilizando la librería <code>cheerio</code> para hacer el parsing del HTML:</p><pre><div class="bg-black rounded-md mb-4"><div class="flex items-center relative text-gray-200 bg-gray-800 px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>javascript</span></div></div></pre></div></div></div><div class="flex flex-grow flex-col gap-3" style="text-align: justify;"><div class="min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap"><div class="markdown prose w-full break-words dark:prose-invert light"><pre><div class="bg-black rounded-md mb-4"><div class="p-4 overflow-y-auto"><code class="!whitespace-pre hljs language-javascript"><span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">'request'</span>);
<span class="hljs-keyword">const</span> cheerio = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cheerio'</span>);
<span class="hljs-keyword">const</span> url = <span class="hljs-string">'https://www.ejemplo.com'</span>;
<span class="hljs-title function_">request</span>(url, <span class="hljs-keyword">function</span>(<span class="hljs-params">error, response, html</span>) {
<span class="hljs-keyword">if</span> (!error && response.<span class="hljs-property">statusCode</span> == <span class="hljs-number">200</span>) {
<span class="hljs-keyword">const</span> $ = cheerio.<span class="hljs-title function_">load</span>(html);
<span class="hljs-comment">// Aquí puedes utilizar la API de cheerio para buscar y extraer datos del HTML</span>
<span class="hljs-comment">// Por ejemplo:</span>
$(<span class="hljs-string">'h1'</span>).<span class="hljs-title function_">each</span>(<span class="hljs-keyword">function</span>(<span class="hljs-params">i, element</span>) {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>($(<span class="hljs-variable language_">this</span>).<span class="hljs-title function_">text</span>());
});
}
});
</code></div></div></pre><p>Este esqueleto utiliza la librería <code>request</code> para hacer una petición HTTP GET al sitio web especificado en la variable <code>url</code>, y luego utiliza <code>cheerio</code> para hacer el parsing del HTML y buscar los elementos que deseas extraer.</p><p>Ten en cuenta que este es solo un esqueleto básico y deberás adaptarlo a tus necesidades específicas. Además, ten en cuenta que algunos sitios web pueden bloquear o limitar las peticiones de web scraping, por lo que debes tener cuidado de no violar los términos de servicio o las políticas de uso de los sitios web que visites.</p></div></div></div><div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto" style="text-align: justify;"><div class="w-[30px] flex flex-col relative items-end"><div class="relative flex"><span style="background: none; border: 0px none; box-sizing: border-box; display: inline-block; height: initial; margin: 0px; max-width: 100%; opacity: 1; overflow: hidden; padding: 0px; position: relative; width: initial;"><span style="background: none; border: 0px none; box-sizing: border-box; display: block; height: initial; margin: 0px; max-width: 100%; opacity: 1; padding: 0px; width: initial;"><br /></span></span></div></div></div><div class="flex flex-grow flex-col gap-3" style="text-align: justify;"><div class="min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap"><b>YO </b>- puedes hacerlo asíncrono?</div></div><div class="group w-full text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]" style="text-align: justify;"><div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto"><div class="w-[30px] flex flex-col relative items-end"><div class="relative h-[30px] w-[30px] p-1 rounded-sm text-white flex items-center justify-center" style="background-color: #10a37f;"><svg class="h-6 w-6" fill="none" height="41" stroke-width="1.5" viewbox="0 0 41 41" width="41" xmlns="http://www.w3.org/2000/svg"></svg></div></div></div></div><div class="flex flex-grow flex-col gap-3" style="text-align: justify;"><div class="min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap"><div class="markdown prose w-full break-words dark:prose-invert light"><p><b>ChatGPT</b> - ¡Por supuesto! Aquí te dejo el mismo esqueleto, pero utilizando la versión asincrónica de <code>request</code> y <code>cheerio</code>:</p><pre><div class="bg-black rounded-md mb-4"><div class="flex items-center relative text-gray-200 bg-gray-800 px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>javascript</span></div></div></pre></div></div></div><div class="flex flex-grow flex-col gap-3" style="text-align: justify;"><div class="min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap"><div class="markdown prose w-full break-words dark:prose-invert light"><pre><div class="bg-black rounded-md mb-4"><div class="p-4 overflow-y-auto"><code class="!whitespace-pre hljs language-javascript"><span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">'request-promise-native'</span>);
<span class="hljs-keyword">const</span> cheerio = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cheerio'</span>);
<span class="hljs-keyword">const</span> url = <span class="hljs-string">'https://www.ejemplo.com'</span>;
(<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span>() {
<span class="hljs-keyword">try</span> {
<span class="hljs-keyword">const</span> html = <span class="hljs-keyword">await</span> <span class="hljs-title function_">request</span>(url);
<span class="hljs-keyword">const</span> $ = cheerio.<span class="hljs-title function_">load</span>(html);
<span class="hljs-comment">// Aquí puedes utilizar la API de cheerio para buscar y extraer datos del HTML</span>
<span class="hljs-comment">// Por ejemplo:</span>
$(<span class="hljs-string">'h1'</span>).<span class="hljs-title function_">each</span>(<span class="hljs-keyword">function</span>(<span class="hljs-params">i, element</span>) {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>($(<span class="hljs-variable language_">this</span>).<span class="hljs-title function_">text</span>());
});
} <span class="hljs-keyword">catch</span> (error) {
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">error</span>(error);
}
})();
</code></div></div></pre><p>Este esqueleto utiliza la versión asincrónica de <code>request</code> llamada <code>request-promise-native</code> para hacer la petición HTTP GET y retorna una promesa que puede ser manejada con <code>async/await</code>. Luego utiliza <code>cheerio</code> de la misma manera para hacer el parsing del HTML y buscar los elementos que deseas extraer.</p><p>Ten en cuenta que, al igual que con la versión síncrona, algunos sitios web pueden bloquear o limitar las peticiones de web scraping, por lo que debes tener cuidado de no violar los términos de servicio o las políticas de uso de los sitios web que visites.</p></div></div></div><div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto" style="text-align: justify;"><div class="w-[30px] flex flex-col relative items-end"><div class="relative flex"><span style="background: none; border: 0px none; box-sizing: border-box; display: inline-block; height: initial; margin: 0px; max-width: 100%; opacity: 1; overflow: hidden; padding: 0px; position: relative; width: initial;"><span style="background: none; border: 0px none; box-sizing: border-box; display: block; height: initial; margin: 0px; max-width: 100%; opacity: 1; padding: 0px; width: initial;"><br /></span></span></div></div></div><div style="text-align: justify;"><b>YO</b> - como puedo hacer 100 llamadas asíncronas en Node.js dividas en lotes de 10?</div><div class="group w-full text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]" style="text-align: justify;"><div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto"><div class="w-[30px] flex flex-col relative items-end"><div class="relative h-[30px] w-[30px] p-1 rounded-sm text-white flex items-center justify-center" style="background-color: #10a37f;"><svg class="h-6 w-6" fill="none" height="41" stroke-width="1.5" viewbox="0 0 41 41" width="41" xmlns="http://www.w3.org/2000/svg"></svg></div></div></div></div><div class="flex flex-grow flex-col gap-3" style="text-align: justify;"><div class="min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap"><div class="markdown prose w-full break-words dark:prose-invert light"><p><b>ChatGPT</b> - Para hacer 100 llamadas asincrónicas divididas en lotes de 10 en Node.js, puedes utilizar <code>Promise.all()</code> en conjunción con <code>Array.slice()</code> para dividir el arreglo de llamadas en lotes de 10 y luego enviar cada lote en una llamada separada de <code>Promise.all()</code>. Aquí te dejo un ejemplo de cómo podrías hacerlo:</p><pre><div class="bg-black rounded-md mb-4"><div class="flex items-center relative text-gray-200 bg-gray-800 px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>javascript</span></div></div></pre></div></div></div><div class="flex flex-grow flex-col gap-3" style="text-align: justify;"><div class="min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap"><div class="markdown prose w-full break-words dark:prose-invert light"><pre><div class="bg-black rounded-md mb-4"><div class="p-4 overflow-y-auto"><code class="!whitespace-pre hljs language-javascript"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">makeCalls</span>() {
<span class="hljs-comment">// Creamos un arreglo con las 100 llamadas asincrónicas que deseamos hacer</span>
<span class="hljs-keyword">const</span> calls = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>({ <span class="hljs-attr">length</span>: <span class="hljs-number">100</span> }, <span class="hljs-function">(<span class="hljs-params">v, i</span>) =></span> <span class="hljs-title function_">makeAsyncCall</span>(i));
<span class="hljs-comment">// Dividimos el arreglo de llamadas en lotes de 10 y enviamos cada lote en una llamada separada de Promise.all()</span>
<span class="hljs-keyword">const</span> batches = [];
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < calls.<span class="hljs-property">length</span>; i += <span class="hljs-number">10</span>) {
<span class="hljs-keyword">const</span> batch = calls.<span class="hljs-title function_">slice</span>(i, i + <span class="hljs-number">10</span>);
batches.<span class="hljs-title function_">push</span>(batch);
}
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">const</span> batch <span class="hljs-keyword">of</span> batches) {
<span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">all</span>(batch);
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(results);
}
}
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">makeAsyncCall</span>(<span class="hljs-params">index</span>) {
<span class="hljs-comment">// Aquí iría tu llamada asincrónica, por ejemplo:</span>
<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title function_">someAsyncFunction</span>(index);
<span class="hljs-keyword">return</span> result;
}
</code></div></div></pre><p>En este ejemplo, creamos un arreglo con las 100 llamadas asincrónicas que deseamos hacer. Luego, dividimos ese arreglo en lotes de 10 utilizando un bucle <code>for</code> y la función <code>Array.slice()</code>, y enviamos cada lote en una llamada separada de <code>Promise.all()</code>. Finalmente, imprimimos los resultados de cada llamada en la consola.</p><p>Ten en cuenta que, dependiendo del tipo de llamadas asincrónicas que estés haciendo, es posible que necesites ajustar el código para manejar errores, timeouts, etc.</p></div></div></div><div class="flex flex-grow flex-col gap-3" style="text-align: justify;"><div class="min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap"><b>YO</b> - ¿no sería mejor usar una función generadora para los batches y poder ahorrarnos tener que crear otro array de batches en memoria?</div></div><div class="group w-full text-gray-800 dark:text-gray-100 border-b border-black/10 dark:border-gray-900/50 bg-gray-50 dark:bg-[#444654]" style="text-align: justify;"><div class="text-base gap-4 md:gap-6 md:max-w-2xl lg:max-w-xl xl:max-w-3xl p-4 md:py-6 flex lg:px-0 m-auto"><div class="w-[30px] flex flex-col relative items-end"><div class="relative h-[30px] w-[30px] p-1 rounded-sm text-white flex items-center justify-center" style="background-color: #10a37f;"><svg class="h-6 w-6" fill="none" height="41" stroke-width="1.5" viewbox="0 0 41 41" width="41" xmlns="http://www.w3.org/2000/svg"></svg></div></div></div></div><div class="flex flex-grow flex-col gap-3" style="text-align: justify;"><div class="min-h-[20px] flex flex-col items-start gap-4 whitespace-pre-wrap"><div class="markdown prose w-full break-words dark:prose-invert light"><p><b>ChatGPT</b> - Sí, puedes utilizar una función generadora para crear los lotes de llamadas asincrónicas en Node.js sin tener que crear un array adicional en memoria. Aquí te dejo un ejemplo de cómo podrías hacerlo:</p><pre><div class="bg-black rounded-md mb-4"><div class="flex items-center relative text-gray-200 bg-gray-800 px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>javascript</span></div></div></pre></div></div></div><div style="text-align: justify;"><pre><div class="bg-black rounded-md mb-4"><div class="p-4 overflow-y-auto"><code class="!whitespace-pre hljs language-javascript"><span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">makeCalls</span>() {
<span class="hljs-keyword">const</span> calls = <span class="hljs-title class_">Array</span>.<span class="hljs-title function_">from</span>({ <span class="hljs-attr">length</span>: <span class="hljs-number">100</span> }, <span class="hljs-function">(<span class="hljs-params">v, i</span>) =></span> <span class="hljs-title function_">makeAsyncCall</span>(i));
<span class="hljs-keyword">const</span> batchSize = <span class="hljs-number">10</span>;
<span class="hljs-keyword">for</span> <span class="hljs-keyword">await</span> (<span class="hljs-keyword">const</span> batch <span class="hljs-keyword">of</span> <span class="hljs-title function_">batchGenerator</span>(calls, batchSize)) {
<span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> <span class="hljs-title class_">Promise</span>.<span class="hljs-title function_">all</span>(batch);
<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(results);
}
}
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span>* <span class="hljs-title function_">batchGenerator</span>(<span class="hljs-params">iterable, batchSize</span>) {
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i < iterable.<span class="hljs-property">length</span>; i += batchSize) {
<span class="hljs-keyword">yield</span> iterable.<span class="hljs-title function_">slice</span>(i, i + batchSize);
}
}
<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">makeAsyncCall</span>(<span class="hljs-params">index</span>) {
<span class="hljs-comment">// Aquí iría tu llamada asincrónica, por ejemplo:</span>
<span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> <span class="hljs-title function_">someAsyncFunction</span>(index);
<span class="hljs-keyword">return</span> result;
}
</code></div></div></pre></div><p style="text-align: justify;">En este ejemplo, creamos un arreglo con las 100 llamadas asincrónicas que deseamos hacer y un tamaño de lote de 10. Luego, creamos una función generadora llamada <code>batchGenerator</code> que toma el arreglo de llamadas asincrónicas y el tamaño de lote como argumentos, y devuelve los lotes de llamadas utilizando un bucle <code>for</code> y la función <code>Array.slice()</code>. Finalmente, utilizamos un bucle <code>for await</code> para iterar sobre los lotes de llamadas generados por <code>batchGenerator</code> y ejecutamos <code>Promise.all()</code> en cada lote para obtener los resultados de cada llamada asincrónica.</p><p style="text-align: justify;">Ten en cuenta que, al igual que en el ejemplo anterior, es posible que necesites ajustar el código para manejar errores, timeouts, etc. dependiendo del tipo de llamadas asincrónicas que estés haciendo.</p><p></p>Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-6168465570663215053.post-21326425809637872662022-11-29T01:18:00.005-08:002023-04-13T01:24:32.746-07:00Cuando y como aplicar programación asíncrona.<div style="text-align: justify;"><p>Pues aquí estamos de nuevo. Hacía una pandemia que no escribía en este blog; debido a un buen montón de líos e imprevistos.</p><p>En esta entrada quería enumerar las situaciones más comunes, con las que es posible encontrarse, que pueden aprovechar las bondades de la programación asíncrona.</p><span><a name='more'></a></span><br /></div><h4 style="text-align: justify;">1. Dejar el hilo libre para otra solicitud.</h4><p style="text-align: justify;">Esta es una de las más típicas y sencillas. Simplemente se hace una espera (await) a una llamada asíncrona de algún recurso de I/O y, mientras este responde, el hilo puede ser reutilizado por el sistema para ir ejecutando otra solicitud; una vez tenemos la respuesta asíncrona, ejecutamos el resto del código. Un ejemplo típico de esto podría ser el código personalizado ejecutado por un servidor http que va procesando los request que llegan.<br /></p><p style="text-align: justify;"><br /></p><h4 style="text-align: justify;">2. Continuar trabajando.</h4><div style="text-align: justify;">Esta es un pelín más complicada, puesto que hay que darse cuenta de la situación y estructurar el código acorde a esa situación.</div><div style="text-align: justify;"> </div><div style="text-align: justify;">Básicamente, si ves que puedes ejecutar algo de código que no depende de la respuesta de la llamada asíncrona, deberías lanzar la llamada asíncrona primero, sin esperar, y, seguidamente, ejecutar el código no dependiente (ergo, mientras se está procesando la llamada asíncrona; y por lo tanto ganamos tiempo).</div><div style="text-align: justify;"> </div><div style="text-align: justify;">Qué hacer con la llamada asíncrona, al final, depende de un factor:</div><div style="text-align: justify;"> </div><div style="text-align: justify;">Si seguimos necesitando ejecutar más código en la función, que sí depende de la respuesta asíncrona; debemos esperar (await) esa respuesta y ejecutar el resto del código.</div><div style="text-align: justify;"><br /></div><div style="text-align: justify;">Si no existe una dependencia de código en la función; podríamos usar <b>callbacks</b>, en vez de esperar (await), para dejar la función que estamos programando mas limpia; quitándonos de en medio la gestión de la respuesta asíncrona al delegarla en el callback. </div><div style="text-align: justify;"> </div><div style="text-align: justify;"><br /></div><div style="text-align: justify;"><h4>3. Lotes de trabajos asíncronos independientes.</h4><div>En este caso nos encontramos con una serie de trabajos asíncronos que no dependen unos de otros. En este caso, realizar una espera (await) de cada uno o de incluso de todos (awaitAll) es contraproducente puesto que de seguro estamos haciendo que la CPU esté parada en algunos momentos. </div><div><br /></div><div>La estrategia a seguir aquí sería lanzar todas las tareas asíncronas sin esperas y confiar en los callbacks de cada una para procesar su respuesta.</div><div><br /></div><div>Un ejemplo típico de esto podría ser un web scrapper que realiza solicitudes a una URL con el número de página o letra del abecedario para recorrer todo el contenido interesante de un sitio.</div><div><br /></div><div>Esto ganaría en complejidad si tenemos tantos trabajos asíncronos que no los pudiésemos lanzar todos a la vez y tengamos que planear una estrategia de lanzarlos de 10 en 10 o incluso mejor; lanzar un nuevo a medida que otro acaba.<br /></div></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-69338356960619684662020-03-12T04:50:00.000-07:002020-03-28T09:09:17.986-07:00Cuando tienes un martillo (TypeScript) ... <div style="text-align: justify;">
todo parecen clavos (tipos). </div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Estaba buscando como se hacen uniones discriminadas y pattern matching con typescript y me he encontrado con artículo en un blog con una solución para "reducir la cantidad de código que tenemos que escribir en una funcion <i>reduce </i>de Redux sin comprometer la seguridad de tipos".</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Traducido al castellano significa que así pulsamos menos teclas para construir la función <i>reduce</i> porque al estar fuertemente tipado salen las letritas y los cartelitos de autocompletado del intellisense del <a href="https://code.visualstudio.com/">VS Code</a>.</div>
<div style="text-align: justify;">
</div>
<a name='more'></a><span style="text-align: justify;">Para conseguirlo, el autor ha escrito el siguiente código:</span><br />
<br />
<pre style="background: #1e1e1e; color: gainsboro; font-family: JetBrains Mono; font-size: 13px;"><span style="color: #569cd6;">enum</span> <span style="color: #b8d7a3;">ActionTypes</span> {
REQUEST_SUCCESS <span style="color: #b4b4b4;">=</span> <span style="color: #d69d85;">"REQUEST_SUCCESS"</span>,
REQUEST_FAILURE <span style="color: #b4b4b4;">=</span> <span style="color: #d69d85;">"REQUEST_FAILURE"</span>,
}
<span style="color: #569cd6;">type</span> SFA<T, P> <span style="color: #b4b4b4;">=</span> { type<span style="color: #b4b4b4;">:</span> T, payload<span style="color: #b4b4b4;">:</span> P };
<span style="color: #569cd6;">const</span> createAction <span style="color: #b4b4b4;">=</span> <T <span style="color: #569cd6;">extends</span> ActionTypes, P>(
type<span style="color: #b4b4b4;">:</span> T,
payload<span style="color: #b4b4b4;">:</span> P
)<span style="color: #b4b4b4;">:</span> SFA<T, P> <span style="color: #b4b4b4;">=></span> ({ type, payload });
<span style="color: #569cd6;">const</span> success <span style="color: #b4b4b4;">=</span> (payload<span style="color: #b4b4b4;">:</span> { items<span style="color: #b4b4b4;">:</span> Todo[] }) <span style="color: #b4b4b4;">=></span>
createAction(ActionTypes.REQUEST_SUCCESS, payload);
<span style="color: #569cd6;">const</span> failure <span style="color: #b4b4b4;">=</span> (payload<span style="color: #b4b4b4;">:</span> { reason<span style="color: #b4b4b4;">:</span> <span style="color: #569cd6;">string</span> }) <span style="color: #b4b4b4;">=></span>
createAction(ActionTypes.REQUEST_FAILURE, payload);
<span style="color: #569cd6;">const</span> actions <span style="color: #b4b4b4;">=</span> { success, failure };
<span style="color: #569cd6;">type</span> Action <span style="color: #b4b4b4;">=</span> ReturnType<<span style="color: #569cd6;">typeof</span> actions[<span style="color: #569cd6;">keyof</span> <span style="color: #569cd6;">typeof</span> actions]>;</pre>
<br />
<div style="text-align: justify;">
1. Define un enumerado con los tipos de acciones.</div>
<div style="text-align: justify;">
2. Define un tipo genérico que contiene un tipo y un payload.</div>
<div style="text-align: justify;">
3. Define una función genérica que crea el tipo genérico anterior (2) restringiendo la entrada de genéricos a los tipos de acción definidos (1)</div>
<div style="text-align: justify;">
4. Define una función por cada tipo de acción; que construye el tipo genérico (2) usando la función genérica (3) y pasando le estáticamente uno de los tipos de acción definidos (1) y el formato del <i>payload.</i></div>
<div style="text-align: justify;">
5. Define una clase que contiene las funciones constructoras (4).</div>
<div style="text-align: justify;">
6. Define una unión discriminada de tipos recorriendo los tipos de retorno de las funciones constructoras almacenadas en la clase anterior (5)</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
ufff... ha costado un ligero esfuerzo seguir el hilo del tema hasta llegar a entender de principio a fin la intención del programador.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
y como se usa:</div>
<br />
<pre style="background: #1e1e1e; color: gainsboro; font-family: JetBrains Mono; font-size: 13px;"><span style="color: #569cd6;">type</span> Todo <span style="color: #b4b4b4;">=</span> { id<span style="color: #b4b4b4;">:</span> <span style="color: #569cd6;">string</span> };
<span style="color: #569cd6;">type</span> State <span style="color: #b4b4b4;">=</span> { items<span style="color: #b4b4b4;">:</span> Todo[], error<span style="color: #b4b4b4;">:</span> <span style="color: #569cd6;">string</span> };
<span style="color: #569cd6;">function</span> reducer(state<span style="color: #b4b4b4;">:</span> State, action<span style="color: #b4b4b4;">:</span> Action)<span style="color: #b4b4b4;">:</span> State {
<span style="color: #569cd6;">switch</span> (action.type) {
<span style="color: #569cd6;">case</span> ActionTypes.REQUEST_SUCCESS:
<span style="color: #569cd6;">return</span> { <span style="color: #b4b4b4;">...</span>state, items: action.payload.items, error: <span style="color: #d69d85;">""</span> };
<span style="color: #569cd6;">case</span> ActionTypes.REQUEST_FAILURE:
<span style="color: #569cd6;">return</span> { <span style="color: #b4b4b4;">...</span>state, items: [], error: action.payload.reason };
}
<span style="color: #569cd6;">return</span> state;
}</pre>
<br />
<div style="text-align: justify;">
Al final termina con un <i>switch case</i> teniendo que preguntar por cada tipo definido en el enumerado; como se ha hecho en programación imperativa toda la vida...</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Todo esto para que en el intellisense del editor de código le salga <i>payload.items</i> o <i>payload.reason</i> en unas cajitas flotantes...</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Tengo que reconocer que el autor tiene que tener bastantes tablas, conocimientos del lenguaje y ser bastante inteligente para llegar a esta complicada solución pero quizás no ha sido suficientemente sagaz como para darse cuenta que es innecesaria y enrevesada.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Ahora pongamos mi implementación orientada a programación funcional sin tipos con javaScript sin florituras:</div>
<br />
<pre style="background: #1e1e1e; color: gainsboro; font-family: JetBrains Mono; font-size: 13px;"><span style="color: #569cd6;">function</span> Action(discriminator) {
<span style="color: #569cd6;">return</span> {
match: (cases) <span style="color: #b4b4b4;">=></span> discriminator(cases)
};
}
<span style="color: #569cd6;">const</span> ActionDiscriminatedUnion <span style="color: #b4b4b4;">=</span> {
fromInsert: (payload) <span style="color: #b4b4b4;">=></span> Action((cases) <span style="color: #b4b4b4;">=></span> cases.insert(payload)),
fromUpdate: (payload) <span style="color: #b4b4b4;">=></span> Action((cases) <span style="color: #b4b4b4;">=></span> cases.update(payload)),
fromDelete: (payload) <span style="color: #b4b4b4;">=></span> Action((cases) <span style="color: #b4b4b4;">=></span> cases.delete(payload))
};</pre>
<br />
1) La funcion <i>Action</i> retorna un objeto que ejecuta la función discriminadora que se le pase por parámetro.<br />
2) ActionDiscrimitatedUnion retorna una <i>Accion </i>(1) con la función discriminadora que le toque.<br />
3) Ya está... no hay nada más que hacer. Va como la seda y es más sencillo que un botijo.<br />
<br />
y como se usa:<br />
<br />
<pre style="background: #1e1e1e; color: gainsboro; font-family: JetBrains Mono; font-size: 13px;"><span style="color: #569cd6;">const</span> state <span style="color: #b4b4b4;">=</span> { items: [<span style="color: #b5cea8;">1</span>, <span style="color: #b5cea8;">2</span>, <span style="color: #b5cea8;">3</span>] };
<span style="color: #569cd6;">const</span> someUnknowAction <span style="color: #b4b4b4;">=</span> ActionDiscriminatedUnion.fromInsert({ items: [<span style="color: #b5cea8;">4</span>, <span style="color: #b5cea8;">5</span>, <span style="color: #b5cea8;">6</span>] });
<span style="color: #569cd6;">function</span> reducer(state, someUnknowAction) {
<span style="color: #569cd6;">return</span> someUnknowAction.match({
insert: (payload) <span style="color: #b4b4b4;">=></span> applyInsert(state, payload),
update: (payload) <span style="color: #b4b4b4;">=></span> applyUpdate(state, payload),
delete: (payload) <span style="color: #b4b4b4;">=></span> applyDelete(state, payload)
});
}
</pre>
<br />
<span style="text-align: justify;">Comparen ustedes....</span><br />
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
En mi opinión el autor ha caído en el sesgo cognitivo de que la solución a un puzle; si es muy compleja, tiene que ser la mejor porque requiere grandes conocimientos, entendimiento y soltura con las herramientas disponibles para solucionar el puzle. Pero no ha caído en que el puzle se lo ha montado él solito y que este no es necesario que exista; por lo que no es necesario una compleja solución a un puzle que podemos quitar de en medio si se hacen bien las cosas.</div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-6168465570663215053.post-12536352336329607982020-03-10T05:29:00.003-07:002021-02-18T02:45:00.653-08:00 Composición de software - Inyeccion de dependencias funcional en la canalización de funciones.<div style="text-align: justify;">
<span style="text-align: justify;">La cosa viene de la </span><a href="https://altplusf13.blogspot.com/2020/03/composicion-de-software-canalizacion-de.html">I</a> y <a href="https://altplusf13.blogspot.com/2020/03/composicion-de-software-canalizacion-de_9.html">II</a> parte<span style="text-align: justify;">. Pégale un vistazo si no lo has hecho todavía.</span></div>
<div style="text-align: justify;">
<span style="text-align: justify;"><br /></span>
<span style="text-align: justify;">En esta entrada voy a demostrar lo fácilmente que se puede modificar y/o extender el comportamiento del workflow de la aplicación y además como se puede hacer inyectando las dependencias para conseguir una arquitectura con bajo acoplamiento.</span></div>
<br />
<a name='more'></a><div style="text-align: justify;">
Voy a modificar el ejemplo de las entradas anteriores para agregar una capa de infraestructura (en este caso; un repositorio externo) para que el workflow de la aplicación incluya el flujo necesario de principio a fin. Con esto demuestro que la canalización de funciones no es solo para el núcleo de la aplicación; si no que se puede usar para ofrecer una interacción de principio a fin con el sistema.</div>
<br />
<div style="text-align: justify;">
Lo primero es crear el repositorio; en este caso voy a crear 2 para demostrar la inyección de dependencias. No os preocupéis ahora sobre el control de errores o la gestión asíncrona para no tener que añadir mas ruido al asunto.</div>
<br />
<pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; color: gainsboro; font-family: "jetbrains mono"; font-size: 13px;"><span style="color: #57a64a;">//module persistence
</span><span style="color: #569cd6;">function</span> redisRepo(config) {
<span style="color: #569cd6;">const</span> redis <span style="color: #b4b4b4;">=</span> <span style="color: #569cd6;">new</span> Redis({
<span style="color: #b4b4b4;">...</span>config
});
<span style="color: #569cd6;">return</span> {
getGameState(pk) {
<span style="color: #569cd6;">return</span> redis.get(pk);
},
setGameState(pk, gameState) {
<span style="color: #569cd6;">return</span> redis.set(pk, gameState);
}
};
}
<span style="color: #569cd6;">function</span> mongoRepo(config) {
<span style="color: #569cd6;">const</span> MongoClient <span style="color: #b4b4b4;">=</span> require(<span style="color: #d69d85;">'mongodb'</span>).MongoClient;
<span style="color: #569cd6;">return</span> {
findGameState(pk) {
<span style="color: #569cd6;">return</span> MongoClient.find(pj);
},
updateGameState(pk, gameState) {
<span style="color: #569cd6;">return</span> MongoClient.updateOne(pk, gameState);<br /> }
}
<span style="color: #569cd6;">const</span> redisPersistence <span style="color: #b4b4b4;">=</span> redisRepo(config);
<span style="color: #569cd6;">const</span> mongoPersistence <span style="color: #b4b4b4;">=</span> mongoRepo(config);</pre>
<br />
<div style="text-align: justify;">
Como se puede observar; los he creado con diferente interfaz para que, más adelante, se pueda apreciar que no es necesario mantener interfaces comunes; por lo cual aporta flexibilidad y contexto ahorrándose el tener que escribir formalmente la definición e implementacion de una interfaz, usar herencias y/o <a href="https://javascript.info/mixins">mixins</a>.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Ahora vamos a agregar el código de orquestación canalizable encargado de leer y escribir de persistencia. Aquí tenemos que inyectar la dependencia al repositorio que vayamos a usar; pero, en vez de inyectar todo el objeto y que el código de orquestación tenga que conocer la interfaz de ese objeto para poderlo usar, vamos a inyectar simplemente la función/es que necesita el código de orquestación para realizar su operación.</div>
<br />
<pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; font-family: "jetbrains mono"; font-size: 13px;"><span style="color: #57a64a;">//module orchestation code
</span><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; color: gainsboro; font-family: "jetbrains mono";"><span style="color: #569cd6;">const</span> readGameState <span style="color: #b4b4b4;">=</span> (readerFnc, { gameStateId }) <span style="color: #b4b4b4;">=></span> {
<span style="color: #569cd6;">const</span> gameState <span style="color: #b4b4b4;">=</span> readerFnc(gameStateId);
<span style="color: #569cd6;">return</span> <span style="color: #b4b4b4;">!</span>gameState <span style="color: #b4b4b4;">?</span>
Either.left(<span style="color: #d69d85;">"no game found in persistence"</span>) <span style="color: #b4b4b4;">:</span>
Either.right({ gameState });
};</pre>
</pre>
<pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; color: gainsboro; font-family: "jetbrains mono"; font-size: 13px;"></pre>
<pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; color: gainsboro; font-family: "jetbrains mono"; font-size: 13px;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: "jetbrains mono";"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: "jetbrains mono";"><span style="color: #569cd6;">const</span> writeGameState <span style="color: #b4b4b4;">=</span> (writerFnc, { gameStateId, gameState }) <span style="color: #b4b4b4;">=></span> {
<span style="color: #569cd6;">const</span> result <span style="color: #b4b4b4;">=</span> writerFnc(gameStateId, gameState);
<span style="color: #569cd6;">return</span> <span style="color: #b4b4b4;">!</span>result <span style="color: #b4b4b4;">?</span>
Either.left(<span style="color: #d69d85;">"unable to write gameState into persistence"</span>) <span style="color: #b4b4b4;">:</span>
Either.right({});
};</pre>
</pre>
</pre>
<br />
<div style="text-align: justify;">
Y ya, a la hora de construir el workflow de la aplicación; simplemente tenemos que seleccionar las funciones que vamos a usar:</div>
<pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; color: gainsboro; font-family: "jetbrains mono"; font-size: 13px;"><span style="color: #569cd6;">const</span> gameReader <span style="color: #b4b4b4;">=</span> redisPersistence.getGameState.bind(redisPersistence);
<span style="color: #569cd6;">const</span> gameWriter <span style="color: #b4b4b4;">=</span> redisPersistence.seGameState.bind(redisPersistence);</pre>
<pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; color: gainsboro; font-family: "jetbrains mono"; font-size: 13px;"></pre>
<pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; color: gainsboro; font-family: "jetbrains mono"; font-size: 13px;"></pre>
<pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; color: gainsboro; font-family: "jetbrains mono"; font-size: 13px;"></pre>
<br />
<div style="text-align: justify;">
Y modificar el workflow de forma trivial poniendo al principio y al final las funciones de infraesctructura nuevas: que pueden ser reutilizadas como cualquier otra. Fijaos como ahora el valor de <i>gameState </i>es inyectado en la canalización por la función de orquestación <i>readGameState </i>y por lo tanto; el punto de entrada al workflow es simplemente el identificador en persistencia del <i>gameState</i>. Awesome!</div><br /><pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; color: gainsboro; font-family: "jetbrains mono"; font-size: 13px;"><pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: "jetbrains mono";"><span style="color: #569cd6;">const</span> executeOperationWorkFlow <span style="color: #b4b4b4;">=</span> pipe(
<span style="color: #57a64a;">//pass the gameState reader to the orchestation function
</span> readGameState.bind(undefined, gameReader)
, checkIsNotPlayerTurn
, obtainOperationCost
, checkNotEnoughEnergy
, executeOperation
<span style="color: #57a64a;">//pass the gameState writer to the orchestation function
</span> , writeGameState.bind(undefined, gameWriter)
);
<span style="color: #569cd6;">const</span> endTurnWorkFlow <span style="color: #b4b4b4;">=</span> pipe(
<span style="color: #57a64a;">//pass the gameState reader to the orchestation function
</span> readGameState.bind(undefined, gameReader)
, checkIsNotPlayerTurn <span style="color: #57a64a;">//reusing orchestation fncs for any workflow we need
</span> , endTurn
<span style="color: #57a64a;">//pass the gameState writer to the orchestation function
</span> , writeGameState.bind(undefined, gameWriter)
);
<span style="color: #57a64a;">//how to use
</span> <span style="color: #569cd6;">let</span> result <span style="color: #b4b4b4;">=</span> executeOperationWorkFlow({ gameStateId, player, operation, target });
result.cata(
<span style="color: #57a64a;">/*
* outputs: "Failed because no game found in persistence"
* OR
* "Failed because it is not player turn"
OR
"Failed because player has not enough energy...."
OR
"Failed because unable to write gameState into persistence"
*/</span>
failure <span style="color: #b4b4b4;">=></span> <span style="color: #d69d85;">`Failed because </span><span style="color: #569cd6;">${</span>failure<span style="color: #569cd6;">}</span><span style="color: #d69d85;">`</span>,
success <span style="color: #b4b4b4;">=></span> <span style="color: #d69d85;">`Great!. New state of the game after the operation </span><span style="color: #569cd6;">${</span>success.gameState<span style="color: #569cd6;">}</span><span style="color: #d69d85;">`</span>
);
result <span style="color: #b4b4b4;">=</span> endTurnWorkFlow({ gameStateId, player });
result.cata(
<span style="color: #57a64a;">/*
* outputs: "Failed because no game found in persistence"
OR
"Failed because it is not player turn"
OR
"Failed because unable to write gameState into persistence"
*/</span>
failure <span style="color: #b4b4b4;">=></span> <span style="color: #d69d85;">`Failed because </span><span style="color: #569cd6;">${</span>failure<span style="color: #569cd6;">}</span><span style="color: #d69d85;">`</span>,
success <span style="color: #b4b4b4;">=></span> <span style="color: #d69d85;">`Great!. New state of the game after the operation </span><span style="color: #569cd6;">${</span>success.gameState<span style="color: #569cd6;">}</span><span style="color: #d69d85;">`</span>
);</pre>
<pre style="background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: "jetbrains mono";"></pre>
</pre>
<br />
<div style="text-align: justify;">
Badaboom! Acabamos de agregar persistencia al workflow de nuestra aplicación modificando simplemente el punto de montaje de los workflows. Hemos tenido que tirar código nuevo claro, pero las modificaciones de lo existente se han mantenido al mínimo, centradas en un punto en concreto y con una única razón de peso para hacerlo.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Y la guinda del pastel es la inyección de dependencias. Si queremos cambiar la implementación de infraestructura utilizando un motor de persistencia distinto lo único que tenemos que hacer es cambiar la selección de las funciones que usa la orquestación:</div>
<br />
<pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; color: gainsboro; font-family: "jetbrains mono"; font-size: 13px;"><span style="color: #57a64a;">//const gameReader = redisPersistence.getGameState.bind(redisPersistence);
</span><span style="color: #569cd6;">const</span> gameReader <span style="color: #b4b4b4;">=</span> mongoPersistence.findGameState.bind(mongoPersistence);
<span style="color: #57a64a;">//const gameWriter = redisPersistence.seGameState.bind(redisPersistence);
</span><span style="color: #569cd6;">const</span> gameWriter <span style="color: #b4b4b4;">=</span> mongoPersistence.updateGameState.bind(mongoPersistence);</pre>
<br />
<div style="text-align: justify;">
Esto, señores, es SOLID; no es OOP fuertemente tipado pero es SOLID sin discursión alguna.</div>
Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-6168465570663215053.post-83851973307405882732020-03-09T03:45:00.003-07:002021-02-18T02:28:54.929-08:00Composición de software - Canalización de funciones con aridad heterogénea en JavaScript usando mónadas.<div style="text-align: justify;">
La cosa viene de la <a href="https://altplusf13.blogspot.com/2020/03/composicion-de-software-canalizacion-de.html">I parte</a>. Pégale un vistazo si no lo has hecho todavía.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
En esta entrada voy a usar una unión disyuntiva en las funciones orquestables del ejemplo anterior y modificamos la función <i>pipe </i>para que opere con ella.</div>
<br />
<a name='more'></a><br />
<div style="text-align: justify;">
La unión disyuntiva (<a href="https://github.com/monet/monet.js/blob/master/docs/EITHER.md">mónada Either </a>de aquí en adelante) es un tipo de constructo que puede representar 2 tipos de valores pero solo uno de ellos a la vez. Either se divide en parte derecha y parte izquierda; las podemos usar indiscriminadamente para representar lo que queramos. Lo más común cuando representamos un OK y un Fail con esta mónada es; al igual que en los callback de JavaScript; utilizar la parte izquierda para representar el Fail y la derecha para el OK.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Utilizando correctamente sus operaciones podemos enlazar las funciones del workflow configuradas en la función <i>pipe(...fncs) </i>únicamente cuando el tipo contenido en la mónada Either sea su parte derecha. En caso de contener la parte izquierda, se saltaría el enlace y retornaría la parte izquierda sin modificar; esto implica que si sigues haciendo llamadas de enlace de la parte derecha posteriormente; su salida final sería la parte izquierda sin modificar.<br />
<br />
<pre style="background: rgb(30, 30, 30) none repeat scroll 0% 0%; color: gainsboro; font-family: JetBrains Mono; font-size: 13px;"><span style="color: #57a64a;">//module Rules
</span><span style="color: #569cd6;">function</span> isPlayerTurn(gameState, player) {
<span style="color: #569cd6;">return</span> gameState.currentPlayerTurn <span style="color: #b4b4b4;">===</span> player;
}
<span style="color: #569cd6;">function</span> operationCost(operation) {
<span style="color: #569cd6;">return</span> <span style="color: #b5cea8;">2</span>; <span style="color: #57a64a;">//fake value for example purposes
</span>}
<span style="color: #569cd6;">function</span> playerHasEnergy(gameState, cost) {
<span style="color: #569cd6;">return</span> gameState.currentPlayerTurn.energy <span style="color: #b4b4b4;">>=</span> cost;
}
<span style="color: #569cd6;">function</span> executeGameOperation(gameState, operation, target) {
<span style="color: #57a64a;">//make changes needed for the operation and target
</span> <span style="color: #569cd6;">return</span> gameState;
}
<span style="color: #569cd6;">function</span> endGamePlayerTurn(gameState, player) {
<span style="color: #57a64a;">//make changes for end turn
</span> <span style="color: #569cd6;">return</span> gameState;
}
<span style="color: #57a64a;">//module orchestation code, now it returns Either monads. right -> ok, left -> fail
</span><span style="color: #569cd6;">const</span> checkIsNotPlayerTurn <span style="color: #b4b4b4;">=</span> ({ gameState, player }) <span style="color: #b4b4b4;">=></span>
<span style="color: #b4b4b4;">!</span>isPlayerTurn(gameState, player) <span style="color: #b4b4b4;">?</span>
Either.left(<span style="color: #d69d85;">"it is not player turn."</span>) <span style="color: #b4b4b4;">:</span>
Either.right({});
<span style="color: #569cd6;">const</span> obtainOperationCost <span style="color: #b4b4b4;">=</span> ({ operation }) <span style="color: #b4b4b4;">=></span>
Either.right({ cost: operationCost(operation) });
<span style="color: #569cd6;">const</span> checkNotEnoughEnergy <span style="color: #b4b4b4;">=</span> ({ gameState, cost }) <span style="color: #b4b4b4;">=></span>
<span style="color: #b4b4b4;">!</span>playerHasEnergy(gameState, cost) <span style="color: #b4b4b4;">?</span>
Either.left(<span style="color: #d69d85;">"player has not enough energy for requested operation"</span>) <span style="color: #b4b4b4;">:</span>
Either.right({});
<span style="color: #569cd6;">const</span> executeOperation <span style="color: #b4b4b4;">=</span> ({ gameState, operation, target }) <span style="color: #b4b4b4;">=></span>
Either.right({ gameState: executeGameOperation(gameState, operation, target) }); <span style="color: #57a64a;">//will modify gameState
</span>
<span style="color: #569cd6;">const</span> endTurn <span style="color: #b4b4b4;">=</span> ({ gameState, player }) <span style="color: #b4b4b4;">=></span>
Either.right({ gameState: endGamePlayerTurn(gameState, player)} ); <span style="color: #57a64a;">//will modify gameState
</span>
<span style="color: #57a64a;">//now we do not need until checks for shortcircuit
</span><span style="color: #569cd6;">function</span> pipe(<span style="color: #b4b4b4;">...</span>fns) {
<span style="color: #569cd6;">return</span> (input) <span style="color: #b4b4b4;">=></span> {
valueOrError <span style="color: #b4b4b4;">=</span> Either.right(input);
<span style="color: #569cd6;">for</span> (fnc <span style="color: #569cd6;">of</span> fns) {
<span style="color: #57a64a;">/* keeps flatMap all the right parts;
* if some fnc return left the rest of the flatMaps are ignored*/</span>
valueOrError <span style="color: #b4b4b4;">=</span> valueOrError.flatMap(value <span style="color: #b4b4b4;">=></span> fnc(Object.assign(input, value)));
}
<span style="color: #569cd6;">return</span> valueOrError.map((value) <span style="color: #b4b4b4; font-family: "jetbrains mono";">=></span><span style="font-family: "jetbrains mono";"> Object.assign(input, value));
</span> };
}
<span style="color: #569cd6;">const</span> executeOperationWorkFlow <span style="color: #b4b4b4;">=</span> pipe(
checkIsNotPlayerTurn
, obtainOperationCost
, checkNotEnoughEnergy
, executeOperation
);
<span style="color: #569cd6;">const</span> endTurnWorkFlow <span style="color: #b4b4b4;">=</span> pipe(
checkIsNotPlayerTurn <span style="color: #57a64a;">//reusing orchestation fncs for any workflow we need
</span> , endTurn
);
<span style="color: #57a64a;">//how to use
</span><span style="color: #569cd6;">let</span> result <span style="color: #b4b4b4;">=</span> executeOperationWorkFlow({ gameState, player, operation, target });
result.cata(
<span style="color: #57a64a;">/*
* outputs: "Failed because it is not player turn"
OR
"Failed because player has not enough energy...."
*/</span>
failure <span style="color: #b4b4b4;">=></span> <span style="color: #d69d85;">`Failed because </span><span style="color: #569cd6;">${</span>failure<span style="color: #569cd6;">}</span><span style="color: #d69d85;">`</span>,
success <span style="color: #b4b4b4;">=></span> <span style="color: #d69d85;">`Great!. New state of the game after the operation </span><span style="color: #569cd6;">${</span>success.gameState<span style="color: #569cd6;">}</span><span style="color: #d69d85;">`</span>
);
result <span style="color: #b4b4b4;">=</span> endTurnWorkFlow({ gameState, player });
result.cata(
<span style="color: #57a64a;">//outputs: Failed because it is not player turn
</span> failure <span style="color: #b4b4b4;">=></span> <span style="color: #d69d85;">`Failed because </span><span style="color: #569cd6;">${</span>failure<span style="color: #569cd6;">}</span><span style="color: #d69d85;">`</span>,
success <span style="color: #b4b4b4;">=></span> <span style="color: #d69d85;">`Great!. New state of the game after the operation </span><span style="color: #569cd6;">${</span>success.gameState<span style="color: #569cd6;">}</span><span style="color: #d69d85;">`</span>
);</pre>
<br />
<br /></div>
<div style="text-align: justify;">
Repasemos como nos ha quedado:</div>
<div style="text-align: justify;">
<br />
- Cortocircuitado del work flow automático.<br />
<div style="text-align: start;">
- Obtención de la razón del cortocircuitado y posibilidad de actuar en consecuencia sin necesidad de estructuras de control condicional.</div>
- Hemos eliminado todas las estructuras de control condicional en el código de orquestación. Gracias a esto hemos reducido la <a href="https://en.wikipedia.org/wiki/Cyclomatic_complexity">complejidad ciclomática</a> drásticamente. Esto significa código mas sencillo, mas fácil de leer, menos posibles bugs y por lo tanto una gran mantenibilidad.<br />
<br />
El engine del juego completo que hice; del que estoy sacando estos ejemplos; tiene exactamente 1 <i>else </i>en más de 1260 líneas de código por las que puede pasar el flujo de ejecución cada vez que el usuario realiza una operación y el <i>if </i>más complejo que tengo se compone de tan solo 3 líneas de código. Es verdad que, además de esta técnica de composición de software, me estoy apoyando en otras técnicas como las <a href="https://rxjs.dev/">extensiones reactivas</a> para eliminar condiciones de flujo de control pero casi puedo asegurar que esta técnica me ha quitado perfectamente el 50% de ellas. Y al fin y al cabo, <a href="http://introtorx.com/Content/v1.0.10621.0/10_LeavingTheMonad.html">las extensiones reactivas son una mónada tambien</a> :-D</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-64493971774239769682020-03-05T23:17:00.003-08:002020-03-28T09:00:00.868-07:00Composición de software - Canalización de funciones con aridad heterogénea en JavaScript<div style="text-align: justify;">
La canalización de funciones es una de las partes más importantes para el desarrollo de software usando el paradigma de programación funcional.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Si revisas las entradas del gran <a href="https://medium.com/@_ericelliott">Eric Elliott en Medium</a> verás una línea de código extremadamente sagaz:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<span style="background-color: rgba(0 , 0 , 0 , 0.05); color: rgba(0 , 0 , 0 , 0.84); font-family: "menlo" , "monaco" , "courier new" , "courier" , monospace; font-size: 16px; letter-spacing: -0.352px; white-space: pre-wrap;">const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);</span></div>
<div style="text-align: justify;">
<br />
<a name='more'></a><br /></div>
<div style="text-align: justify;">
Como se puede observar; la función pipe retorna una función que aplica una reducción ejecutadora al arreglo de funciones pasadas por parámetro partiendo del valor inicial.<br />
</div>
<div style="text-align: justify;">
La idea es bastante guay pero; para que el efecto "enlightenment" sea más pronunciado gracias a que lo ha conseguido hacer en una sola línea de código; el autor ha omitido unos cuantos matices que resultan en que la utilidad de esta función se reduzca considerablemente hasta un límite casi marginal.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Lo ideal es que las funciones no tengan por que saber que van a ser consumidas siguiendo un contrato preestablecido (en este caso, que van a ser usadas mediante la canalización) y por lo tanto puedan cumplir con su responsabilidad única sin preocuparse del exterior.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Como se puede apreciar en el código de arriba, para que las funciones se puedan canalizar; la salida de una función tiene que ser de cierta manera "compatible" con la entrada de la siguiente función. En javaScript siempre puedes aceptar y retornar un objeto como "bolsa de información" y cada función puede acceder solo a la parte de la "bolsa" que necesita y retornar otra bolsa modificada aplicando sus reglas pertinentes pero eso implica que la función tiene que ser modelada explícitamente para manejar la "bolsa" de entrada y para retornar la "bolsa" modificada (u otra bolsa nueva con la nueva información si vamos en plan inmutable).</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
A mi me gustaría que la función pudiese solo recibir la información en la que está interesada y pudiese devolver la información que ha calculado y/o modificado. Esto se puede solucionar decorando la función original sin tener que modificarla.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Otro problema que tiene es el control de errores. La propuesta de Eric no tiene ningún control para el cortocircuito del flujo de ejecución y asume que todas las salidas de las funciones van a ser operaciones correctas o por lo menos salidas procesables como monadas <a href="https://github.com/monet/monet.js/blob/master/docs/MAYBE.md">Maybe</a> o <a href="https://github.com/monet/monet.js/blob/master/docs/EITHER.md">Either</a>.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Desgraciadamente JavaScript no ofrece todas las facilidades necesarias para cumplir al 100% estas características pero de seguro que se puede mejorar utilizando la desestructuración de objetos literales y la asignación de propiedades enumerables.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Para que podamos obtener los resultados debemos crear funciones de orquestación que decoren las funciones del nucleo principal del dominio y debemos modificar ligeramente la función presentada por Eric para el cortocircuitado.<br />
<br />
<pre style="background: rgb(30, 30, 30); color: gainsboro;"><pre><pre><pre><span style="font-family: "courier new" , "courier" , monospace;"><span style="color: #57a64a;">//module Rules
</span><span style="color: #569cd6;">function</span> isPlayerTurn(gameState, player) {
<span style="color: #569cd6;">return</span> gameState.currentPlayerTurn <span style="color: #b4b4b4;">===</span> player;
}
<span style="color: #569cd6;">function</span> operationCost(operation) {
<span style="color: #569cd6;">return</span> <span style="color: #b5cea8;">2</span>; <span style="color: #57a64a;">//fake value for example purposes
</span>}
<span style="color: #569cd6;">function</span> playerHasEnergy(gameState, cost) {
<span style="color: #569cd6;">return</span> gameState.currentPlayerTurn.energy ><span style="color: #b4b4b4;">=</span> cost;
}
<span style="color: #569cd6;">function</span> executeGameOperation(gameState, operation, target) {
<span style="color: #57a64a;">//make changes needed for the operation and target
</span> <span style="color: #569cd6;">return</span> gameState;
}
<span style="color: #569cd6;">function</span> endGamePlayerTurn(gameState, player) {
<span style="color: #57a64a;">//make changes for end turn
</span> <span style="color: #569cd6;">return</span> gameState;
}
<span style="color: #57a64a;">//module orchestation code
</span><span style="color: #569cd6;">const</span> checkIsNotPlayerTurn <span style="color: #b4b4b4;">=</span> ({ gameState, player }) <span style="color: #b4b4b4;">=></span>
<span style="color: #b4b4b4;">!</span>isPlayerTurn(gameState, player) <span style="color: #b4b4b4;">?</span> <span style="color: #569cd6;">null</span> <span style="color: #b4b4b4;">:</span> {};
<span style="color: #569cd6;">const</span> obtainOperationCost <span style="color: #b4b4b4;">=</span> ({ operation }) <span style="color: #b4b4b4;">=></span>
({ cost: operationCost(operation) });
<span style="color: #569cd6;">const</span> checkNotEnoughEnergy <span style="color: #b4b4b4;">=</span> ({ gameState, cost }) <span style="color: #b4b4b4;">=></span>
<span style="color: #b4b4b4;">!</span>playerHasEnergy(gameState, cost) <span style="color: #b4b4b4;">?</span> <span style="color: #569cd6;">null</span> <span style="color: #b4b4b4;">:</span> {};
<span style="color: #569cd6;">const</span> executeOperation <span style="color: #b4b4b4;">=</span> ({ gameState, operation, target }) <span style="color: #b4b4b4;">=></span>
executeGameOperation(gameState, operation, target); <span style="color: #57a64a;">//will modify gameState
</span>
<span style="color: #569cd6;">const</span> endTurn <span style="color: #b4b4b4;">=</span> ({ gameState, player }) <span style="color: #b4b4b4;">=></span>
endGamePlayerTurn(gameState, player); <span style="color: #57a64a;">//will modify gameState
</span>
<span style="color: #57a64a;">//until: (value:any) => boolean - on true pipe will shortcircuit
</span><span style="color: #569cd6;">function</span> pipe(until, <span style="color: #b4b4b4;">...</span>fns) {
<span style="color: #569cd6;">return</span> (input) <span style="color: #b4b4b4;">=></span> {
<span style="color: #569cd6;">for</span> (fnc <span style="color: #569cd6;">of</span> fns) {
<span style="color: #57a64a;">//execute fnc with the output of the previous fnc or with the initial value
</span> temp <span style="color: #b4b4b4;">=</span> fnc(input);
<span style="color: #569cd6;">if</span> (until(temp)) <span style="color: #569cd6;">return</span> temp; <span style="color: #57a64a;">//shortcircuit if true
</span> input <span style="color: #b4b4b4;">=</span> Object.assign(input, temp); <span style="color: #57a64a;">//merge outputs to pass it to next fncs
</span> }
<span style="color: #569cd6;">return</span> input;
};
}
<span style="color: #569cd6;">const</span> pipeUntilNull <span style="color: #b4b4b4;">=</span> pipe.bind(undefined, value <span style="color: #b4b4b4;">=></span> value <span style="color: #b4b4b4;">===</span> <span style="color: #569cd6;">null</span>);
<span style="color: #569cd6;">const</span> executeOperationWorkFlow <span style="color: #b4b4b4;">=</span> pipeUntilNull(
checkIsNotPlayerTurn
, obtainOperationCost
, checkNotEnoughEnergy
, executeOperation
);
<span style="color: #569cd6;">const</span> endTurnWorkFlow <span style="color: #b4b4b4;">=</span> pipeUntilNull(
checkIsNotPlayerTurn <span style="color: #57a64a;">//reusing orchestation fncs for any workflow we need
</span> , endTurn
);
<span style="color: #57a64a;">//how to use
</span><span style="color: #569cd6;">let</span> result <span style="color: #b4b4b4;">=</span> executeOperationWorkFlow({ gameState, player, operation, target });
<span style="color: #569cd6;">if</span> (<span style="color: #b4b4b4;">!</span>result) { console.log(<span style="color: #d69d85;">'unable to perform the requested operation'</span>); }
<span style="color: #569cd6;">else</span> { console.log(<span style="color: #d69d85;">'requested operation succeful applied'</span>); }
result <span style="color: #b4b4b4;">=</span> endTurnWorkFlow({ gameState, player });
<span style="color: #569cd6;">if</span> (<span style="color: #b4b4b4;">!</span>result) { console.log(<span style="color: #d69d85;">'you can not end turn if it is not your turn!!!'</span>); }
<span style="color: #569cd6;">else</span> { console.log(<span style="color: #d69d85;">'player ends turn'</span>); }</span></pre>
</pre>
</pre>
<pre style="font-family: "jetbrains mono"; font-size: 13px;"></pre>
<span style="font-family: "jetbrains mono"; font-size: 13px;">
</span></pre>
<pre style="background: #1e1e1e; color: gainsboro; font-family: JetBrains Mono; font-size: 13px;"></pre>
</div>
<br />
<div style="text-align: justify;">
Repasemos como nos ha quedado:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
- Mantenemos las funciones principales de las reglas del juego con una responsabilidad única e independientes de como van a ser consumidas.</div>
<div style="text-align: justify;">
- Podemos cortar el flujo de trabajo en cualquier momento.</div>
<div style="text-align: justify;">
- Las funciones de orquestación solo usan la información en la que están interesadas.</div>
<div style="text-align: justify;">
- La cadena de inputs/outputs se puede extender. Fijaos como inicialmente no tenemos el valor del coste de la operación (cost) pero la podemos añadir en cualquier momento y a partir de ahí está disponible para cualquier otra función que la necesite.<br />
- Se pueden seguir usando las funciones del Nucleo en cualquier sitio que necesitemos sin que estén condicionadas a como van a ser consumidas.<br />
- Se pueden reutilizar las funciones de orquestación en cualquier otro flujo de trabajo canalizado.<br />
- La programación defensiva esta segregada de la operación principal. Nos aseguramos que si alcanzamos la ejecución de una operación; ésta tiene todo los valores que necesita y que además los valores son correctos y su estado válido para realizar la operación alcanzada.<br />
- La posibilidad de inmutabilidad de la información que maneja (siempre podemos devolver un nuevo gameState en cada paso) aporta confianza ya que sabemos que no se nos filtraran efectos colaterales.</div>
<div style="text-align: justify;">
- La testeabilidad es increíblemente trivial y de resultados muy fiables. Podemos hacer test del módulo de reglas; el dominio principal. Podemos hacer test de las funciones orquestadoras independientes. Y podemos hacer test de la función final que se compone de la canalización de las funciones orquestadoras. Y todo con una responsabilidad única absoluta (Nucleo, orquestación y canalización).</div>
<div style="text-align: justify;">
- La facilidad de lectura del flujo de trabajo es impresionante. Tienes una descripción pasito a pasito de lo que hace el flujo de trabajo de principio a fin. Yo personalmente le he dado a leer a mi señora madre; la cual no tiene ni repajolera idea de software; la función executeOperationWorkFlow y la ha entendido perfectamente.</div>
<div style="text-align: justify;">
- Todo lo anterior repercute en una gran mantenibilidad del código.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Y esto solo con la implementación más necia y tontuela. En entradas posteriores agregaré el uso de mónadas para poder capturar el contexto de cualquier cortocircuito que se produzca y actuar en consecuencia o poder devolver un valor de fallback en caso de necesitarlo; entre otras cosas.</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-77242612853552920852020-02-06T02:02:00.001-08:002020-02-10T01:40:07.565-08:00NodeJs en AppVeyor<div style="text-align: justify;">
Lo que parecía simple y directo al final es una odisea. Seguro que te suena...</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Para poner en marcha un entorno de <a href="https://en.wikipedia.org/wiki/Continuous_integration">CI</a> de NodeJs en AppVeyor hay que tener estas cosas en cuenta:</div>
<div style="text-align: justify;">
<br /></div>
<a name='more'></a><div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Actualizar la vesión de Node instalada en AppVeyor que es viejuna como ella sola. No entiendo porque no tienen actualizados los entornos a la última versión que tenga <a href="https://nodejs.org/en/about/releases/">LTS</a>; que para eso se hacen las versiones LTS señores...</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<pre style="background: #1e1e1e; color: gainsboro; font-family: Consolas; font-size: 13px;"><span style="color: #4ec9b0;">environment:</span><span style="color: #d69d85;">
</span> <span style="color: #4ec9b0;">nodejs_version:</span><span style="color: #d69d85;"> "12"</span></pre>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Configurar toda la integración con los test (<a href="https://mochajs.org/">mocha</a> en este caso) tiene su movida. Si sólo dejas "npm test" en configuración los test se ejecutan, pero simplememente salen por consola; aunque sí que detecta la salida del proceso "mocha" para fallar la build en caso de test fallidos.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<pre style="background: #1e1e1e; color: gainsboro; font-family: Consolas; font-size: 13px;"><span style="color: #4ec9b0;">test_script:</span><span style="color: #d69d85;">
</span> <span style="color: #57a64a;"># Output useful info for debugging.
</span> <span style="color: #b4b4b4;">-</span><span style="color: #d69d85;"> node --version
</span> <span style="color: #b4b4b4;">-</span><span style="color: #d69d85;"> npm --version
</span> <span style="color: #57a64a;"># run tests
</span> <span style="color: #b4b4b4;">-</span><span style="color: #d69d85;"> npm test</span></pre>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Para que los test se persistan y se muestren en la pestaña Test de AppVeyor hay que ejecutar un script (powershell en este caso) para que suba los test desde un fichero a través de la API rest de la plataforma:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<pre style="background: #1e1e1e; color: gainsboro; font-family: Consolas; font-size: 13px;"><span style="color: #b4b4b4;">- </span><span style="color: #4ec9b0;">ps:</span><span style="color: #d69d85;"> >-
$wc = New-Object 'System.Net.WebClient'
$wc.UploadFile("https://ci.appveyor.com/api/testresults/{testType}/$($env:APPVEYOR_JOB_ID)",
(Resolve-Path .\testResultFile))</span></pre>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Así que tenemos que generar un fichero con mocha y además con un formato que acepte AppVeyor (testType). Escojo xunit por su naturaleza generalista y uso extendido.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<pre style="background: #1e1e1e; color: gainsboro; font-family: Consolas; font-size: 13px;"><span style="color: #d7ba7d;">"scripts"</span><span style="color: #b4b4b4;">:</span> <span style="color: #b4b4b4;">{</span>
<span style="color: #d7ba7d;">"start"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"node moonBot.js"</span><span style="color: #b4b4b4;">,</span>
<span style="color: #d7ba7d;">"test"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"mocha --reporter xunit > .\\xunit-results.xml"</span>
<span style="color: #b4b4b4;">}</span></pre>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Y ya está montado... ni por asomo!! Resulta que el comando de redirección ( > ) oculta el output del proceso de test y la plataforma ya no detecta si los test han fallado, así que siempre pondrá el build en correcto.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Hay que utilizar la opción de comando de los reporter de mocha (--reporter-option) para montar el fichero de test:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<pre style="background: #1e1e1e; color: gainsboro; font-family: Consolas; font-size: 13px;"><span style="color: #d7ba7d;">"scripts"</span><span style="color: #b4b4b4;">:</span> <span style="color: #b4b4b4;">{</span>
<span style="color: #d7ba7d;">"start"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"node moonBot.js"</span><span style="color: #b4b4b4;">,</span>
<span style="color: #d7ba7d;">"test"</span><span style="color: #b4b4b4;">:</span> <span style="color: #d69d85;">"mocha --reporter xunit --reporter-option output=.\\xunit-results.xml"</span>
<span style="color: #b4b4b4;">}</span></pre>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Y ya sólo queda poner el script de subida en la sección after_test indicando el tipo de test y el nombre del fichero:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<pre style="background: #1e1e1e; color: gainsboro; font-family: Consolas; font-size: 13px;"><span style="color: #4ec9b0;">after_test:</span><span style="color: #d69d85;">
</span><span style="color: #b4b4b4;">- </span><span style="color: #4ec9b0;">ps:</span><span style="color: #d69d85;"> >-
$wc = New-Object 'System.Net.WebClient'
$wc.UploadFile("https://ci.appveyor.com/api/testresults/xunit/$($env:APPVEYOR_JOB_ID)",
(Resolve-Path .\xunit-results.xml))</span></pre>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Y ya está montado... ni de cerca!! No funciona, no aparece nada en la pestaña test de la plataforma. Resulta que el formato de salida del reporter xUnit de mocha es jUnit!?!?!?! Le echas un vistazo al <a href="https://xunit.net/docs/format-xml-v2">formato de xUnit</a> y no se parecen ni por casualidad. Así que toca cambiar el script de subida para indicar que estás subiendo jUnit:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<pre style="background: #1e1e1e; color: gainsboro; font-family: Consolas; font-size: 13px;"><span style="color: #b4b4b4;">- </span><span style="color: #4ec9b0;">ps:</span><span style="color: #d69d85;"> >-
$wc = New-Object 'System.Net.WebClient'
$wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)",
(Resolve-Path .\xunit-results.xml))</span></pre>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Y ahora sí que lo tenemos... ni de blás pedazo de necio!! Al parecer la sección "after_test" de la plataforma sólo se ejecuta cuando los test se han pasado correctamente... que ya le podrían haber puesto el nombre "after_test_passed" los muy mendrugos y así no hay interpretación errónea posible.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Así que hay que poner el script de subida de test en la sección "on_finish" que es la que se ejecuta tanto si ha fallado la build como si no:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<pre style="background: #1e1e1e; color: gainsboro; font-family: Consolas; font-size: 13px;"><span style="color: #4ec9b0;">on_finish:</span><span style="color: #d69d85;">
</span><span style="color: #b4b4b4;">- </span><span style="color: #4ec9b0;">ps:</span><span style="color: #d69d85;"> >-
$wc = New-Object 'System.Net.WebClient'
$wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)",
(Resolve-Path .\xunit-results.xml))</span></pre>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Arf! Ahora sí que funciona; de momento...</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-28531490480611782512020-01-24T05:13:00.001-08:002020-01-27T00:33:45.553-08:00Mi nuevo pet project.<div style="text-align: justify;">
Un completo engine en node.js y su bot de telegram acompañándolo, que permite jugar al juego de mesa <a href="http://compus.deusto.es/es/moon-1110011/">MOON(1110011)</a>. Habla con el bot para jugar en single player; añade el bot a un grupo y podrás jugar en cooperativo con tus amigos! allá donde estés, sin fronteras!</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<a href="https://github.com/jlvaquero/MoonBot">https://github.com/jlvaquero/MoonBot</a></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Si alguien se siente generoso podría hacer un bot en otras plataformas; slack, messenger, etc utilizando el mismo engine por lo que en un principio lo tiene facilito ;)</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-69243578486771545272019-12-02T04:05:00.001-08:002019-12-13T04:27:21.171-08:00Jerarquía de contenedores de Inyección de dependencias en ASP.NET MVC<div style="text-align: justify;">
¿Has tenido alguna vez un sistema de información; cuya interfaz para la parte interactiva es ASP.NET MVC; que necesita mantener varias versiones de comportamiento en la capa de aplicación y/o negocio a la vez y la decisión de qué versión utilizar tenía que establecerse en tiempo de ejecución según el input del usuario? Si es así, quizás esto te interese.
</div>
<div style="text-align: justify;">
<br /></div>
<a name='more'></a><div style="text-align: justify;">
La mejor solución al problema de versionado de reglas de dominio es la inyección de dependencias (ID). No hay nada mejor para mantener una coherencia y segregación del comportamiento a la vez que reduces la complejidad ciclomática.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
La mayoría de los contenedores de ID permiten una organización jerárquica en la que un contenedor hijo puede agregar y/o sobrescribir la política de inyecciones del contenedor padre. Gracias a esto podemos tener un contenedor hijo por cada versión de la capa de aplicación/dominio que necesitemos y usaremos ese contenedor hijo para resolver las dependencias.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Pero con ASP.NET MVC hemos topado... Si intentas utilizar DependencyResolver.SetResolver por defecto te darás cuenta que solo permite usar un contenedor. Y no puedes cambiar este valor; para usar el contenedor hijo que toque; en cada request porque puede estar siendo utilizado por otro usuario de forma concurrente. A esto se suma la inherente falta de estado del protocolo HTTP. U obligamos al usuario a introducir el valor que nos permite inferir la versión de las reglas que vamos a usar en cada input de cada acción realizada o creamos un estado mutable en sesión asignado a ese usuario. Ambas soluciones son horrorosas y tienen consecuencias desastrosas para la experiencia del usuario y la navegabilidad de la aplicación.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Para solucionar el problema del input se me ha ocurrido que podemos abstraerlo a un valor incluido en las rutas virtuales de MVC. Gracias al input especificado en la ruta; podemos inferir que versión de reglas debemos resolver. También permitiría crear cualquier hiperenlace con ese valor por lo que el usuario está mandando en cada request (ya sea post o get) el input discriminatorio de reglas casi sin que se de cuenta.</div>
<div>
<div style="text-align: justify;">
<br /></div>
</div>
<div>
<div style="text-align: justify;">
Para hacer que ASP.NET MVC utilice varios contenedores de dependencias tendríamos que crear nuestro IDependencyResolver personalizado. Allí podemos recuperar el contenedor hijo que le corresponda según el valor de la información de la ruta virtual y usarlo para resolver las dependencias.</div>
<div style="text-align: justify;">
<br /></div>
<pre style="background: rgb(30, 30, 30); font-family: Consolas; font-size: 13px;"><span style="color: gainsboro;"><span style="color: #569cd6;">Public</span> <span style="color: #569cd6;">Class</span> <span style="color: #4ec9b0;">MyDependencyResolver</span>
<span style="color: #569cd6;">Implements</span> <span style="color: #b8d7a3;">IDependencyResolver</span>
<span style="color: #569cd6;">Private</span> childContainers <span style="color: #569cd6;">As</span> <span style="color: #569cd6;">New</span> <span style="color: #4ec9b0;">Dictionary</span>(<span style="color: #569cd6;">Of</span> <span style="color: #569cd6;">String</span>, <span style="color: #b8d7a3;">IUnityContainer</span>)
<span style="color: #569cd6;">Public</span> <span style="color: #569cd6;">Sub</span> <span style="color: #569cd6;">New</span>(container <span style="color: #569cd6;">As</span> <span style="color: #b8d7a3;">IUnityContainer</span>)
<span style="color: #4ec9b0;">ServicesConfig</span><span style="color: #b4b4b4;">.</span>RegisterTypes(container)
childContainers<span style="color: #b4b4b4;">.</span>Add(<span style="color: #569cd6;">String</span><span style="color: #b4b4b4;">.</span>Empty, container)
<span style="color: #569cd6;">Dim</span> child <span style="color: #b4b4b4;">=</span> container<span style="color: #b4b4b4;">.</span>CreateChildContainer()
<span style="color: #4ec9b0;">ServicesConfig</span><span style="color: #b4b4b4;">.</span>RegisterCampania20018Types(child)
childContainers<span style="color: #b4b4b4;">.</span>Add(<span style="color: #d69d85;">"2018"</span>, child)
child <span style="color: #b4b4b4;">=</span> container<span style="color: #b4b4b4;">.</span>CreateChildContainer()
<span style="color: #4ec9b0;">ServicesConfig</span><span style="color: #b4b4b4;">.</span>RegisterCampania20019Types(child)
childContainers<span style="color: #b4b4b4;">.</span>Add(<span style="color: #d69d85;">"2019"</span>, child)
<span style="color: #569cd6;">End</span> <span style="color: #569cd6;">Sub</span>
<span style="color: #569cd6;">Private</span> <span style="color: #569cd6;">Function</span> IDependencyResolver_GetService(serviceType <span style="color: #569cd6;">As</span> <span style="color: #4ec9b0;">Type</span>) <span style="color: #569cd6;">As</span> <span style="color: #569cd6;">Object</span> <span style="color: #569cd6;">Implements</span> <span style="color: #b8d7a3;">IDependencyResolver</span><span style="color: #b4b4b4;">.</span>GetService
<span style="color: #569cd6;">Dim</span> campania <span style="color: #b4b4b4;">=</span> <span style="color: #4ec9b0;">HttpContext</span><span style="color: #b4b4b4;">.</span>Current<span style="color: #b4b4b4;">.</span>Request<span style="color: #b4b4b4;">.</span>RequestContext<span style="color: #b4b4b4;">.</span>RouteData?<span style="color: #b4b4b4;">.</span>Values(</span><span style="color: #d69d85;">"myDiscriminatorRouteValue"</span><span style="color: gainsboro;">)</span><span style="color: gainsboro;">
</span><span style="color: #569cd6;">If</span><span style="color: gainsboro;"> </span><span style="color: #569cd6;">String</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">IsNullOrEmpty(campania) </span><span style="color: #569cd6;">Then</span><span style="color: gainsboro;"> </span><span style="color: #569cd6;">Throw Exception</span><span style="color: gainsboro;">
</span><span style="color: #569cd6;">Return</span><span style="color: gainsboro;"> childContainers(campania)</span><span style="color: #b4b4b4;">.</span><span style="color: gainsboro;">Resolve(serviceType)
</span><span style="color: #569cd6;">End</span><span style="color: gainsboro;"> </span><span style="color: #569cd6;">Function</span><span style="color: gainsboro;">
</span><span style="color: gainsboro;">
</span><span style="color: #569cd6;">End</span><span style="color: gainsboro;"> </span><span style="color: #569cd6;">Class</span></pre>
</div>
<br />
<div style="text-align: justify;">
Pero nos encontramos con una movida muy chunga y es que <span style="font-family: "consolas"; font-size: 13px;">HttpContext.Current.Request.RequestContext.RouteData.Values(</span><span style="font-family: "consolas"; font-size: 13px;">"myDiscriminatorRouteValue"</span><span style="font-family: "consolas"; font-size: 13px;">) </span>está vacío! Da igual que definamos la rutas por código o por atributo en los métodos del controlador; da igual que usemos prefijos de ruta en el controlador o no; siempre siempre te encontrarás que en este momento del pipeline de ASP.NET MVC RouteData no tiene mapeado los valores de las rutas... o sí?</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Resulta que están escondidas por vete tú a saber que deuda técnica bizarra. Para acceder a los valores de las rutas dentro de un DependencyResolver hay que excavar más a fondo y nos las encontramos aquí:</div>
<br />
<pre style="background: #1e1e1e; color: gainsboro; font-family: Consolas; font-size: 13px;"><span style="color: #4ec9b0;">HttpContext</span><span style="color: #b4b4b4;">.</span>Current<span style="color: #b4b4b4;">.</span>Request<span style="color: #b4b4b4;">.</span>RequestContext<span style="color: #b4b4b4;">.</span>RouteData?</pre>
<pre style="background: #1e1e1e; color: gainsboro; font-family: Consolas; font-size: 13px;"><span style="color: #b4b4b4;">.</span>Values(<span style="color: #d69d85;">"MS_DirectRouteMatches"</span>)?(<span style="color: #b5cea8;">0</span>)?<span style="color: #b4b4b4;">.</span>Values?(<span style="color: #d69d85;">"myDiscriminatorRouteValue"</span>)
</pre>
<br />
<div style="text-align: justify;">
Ale. Ahora no quiero ver un solo IF que intente discriminar comportamientos en la capa de aplicación o dominio. Cada versión tiene su clase que inyectamos gracias al contenedor de ID y ya solo queda llamarlo a piñón.</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-80925702686632128042019-10-17T04:56:00.001-07:002019-11-26T22:38:42.809-08:00Retryer funcional<div style="text-align: justify;">
Hasta ahora estaba utilizando PostSharp para mi infraestructura de <a href="https://samples.postsharp.net/f/PostSharp.Samples.AutoRetry/AutoRetryAttribute.cs.html">reintentos</a> y muy contento oye. Utilizar AOP para estas cosas de<b> </b><span style="background-color: white;"><span style="color: #222222; font-family: inherit;">crosscutting concerns está muy guapo y solo con eso ya está a milenios luz de la </span><span style="color: #222222;">mayoría</span><span style="color: #222222; font-family: inherit;"> de los proyectos laborales que te puedes encontrar.</span></span></div>
<div style="text-align: justify;">
<span style="background-color: white;"><span style="color: #222222; font-family: inherit;"><br /></span></span>
<span style="color: #222222;"><span style="background-color: white;">Pero tenía un problema y es que no puedes modificar en tiempo de ejecución nada. No puedes decidir si una función se reintenta o no y no puedes cambiar los parámetros del reintento. Todo está decidido en tiempo de diseño y no hay manera de cambiarlo en tiempo de ejecución. Tampoco se puede tener la misma función decorada con 2 retryers diferentes que sean usadas por distintas partes del sistema.</span></span></div>
<div style="text-align: justify;">
<br /></div>
<a name='more'></a><div style="text-align: justify;">
Dándole vueltas he pensado que se podría utilizar un patrón decorador para configurar las funciones en tiempo de ejecución; dándole más vueltas he pensado que con programación funcional puedes hacer un decorador mucho más guapo y flexible que con OOP y dándole más vueltas todavía he pensado que se puede refactorizar el código para no repetir ni una sola línea de código gracias a la programación funcional.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
El problema es que me surgen dudas sobre hasta que punto esta implementación puede ocasionar problemas o si tengo que tener en cuenta el uso de <a href="https://docs.microsoft.com/es-es/dotnet/api/system.threading.tasks.task.configureawait?view=netframework-4.8">ConfigureAwait</a> (lo uso?, no lo uso?, parametrizo su uso?, si alguien que usa este código y aplica ConfigureAwait rompe algo?).</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Resumiendo; aquí os dejo la implementación. Si le pegáis un vistazo os agradecería que me comentaseis todos los matices o problemas que se os ocurran para ir mejorando esto y poder darle el visto bueno para producción algún día.</div>
<br />
<div style="text-align: justify;">
<script class="brush: csharp; highlight: [107]" type="syntaxhighlighter">
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace RetryPrototypeAlpha {
class RetrierTest {
public static async Task Main() {
DoWork worker1 = new DoWork("doWork instance1");
DoWork worker2 = new DoWork("doWork instance2");
Action doWorkActn = () => worker1.WorkerAction(1, "hello");
Func<int> doWorkFnc = () => worker2.WorkerFuntion(2, "hello");
Func<task> asyncDoWorkActn = () => worker1.AsyncWorkerAction(3, "hello");
Func<Task<int>> asyncDoWorkFnc = () => worker2.AsyncWorkerFunction(4, "hello");
CancellationToken cancelOperation = new CancellationTokenSource().Token;
try {
Task<int> t4 = Retryer.Retry(asyncDoWorkFnc, 3, 3000, new[] { typeof(InvalidOperationException), typeof(InvalidCastException) }, cancelOperation);
Task t3 = Retryer.Retry(asyncDoWorkActn, 3, 3000, new[] { typeof(InvalidOperationException), typeof(InvalidCastException) }, cancelOperation);
Task t1 = Retryer.Retry(doWorkActn, 3, 3000, new[] { typeof(InvalidOperationException), typeof(InvalidCastException) }, cancelOperation);
Task<int> t2 = Retryer.Retry(doWorkFnc, 3, 3000, new[] { typeof(InvalidOperationException), typeof(InvalidCastException) }, cancelOperation);
await Task.WhenAll(new[] { t1, t2, t3, t4 });
int a = t4.Result;
int b = t2.Result;
CancellationToken cancelMyCustomOperation = new CancellationTokenSource().Token;
Retryer MyCustomRetryer = new Retryer(3, 1000, new[] { typeof(InvalidOperationException) }, cancelMyCustomOperation);
t4 = MyCustomRetryer.Retry(asyncDoWorkFnc);
t3 = MyCustomRetryer.Retry(asyncDoWorkActn);
t1 = MyCustomRetryer.Retry(doWorkActn);
t2 = MyCustomRetryer.Retry(doWorkFnc);
await Task.WhenAll(new[] { t1, t2, t3, t4 });
a = t4.Result;
b = t2.Result;
}
catch (Exception ex) {
Console.WriteLine(ex.Message);
}
Console.WriteLine("Press any key to close...");
Console.ReadKey();
}
}
public class DoWork {
public DoWork(string value) {
this.instanceValue = value;
}
private string instanceValue;
public async Task<int> AsyncWorkerFunction(int arg1, string arg2) {
Console.WriteLine("Entering AsyncWorkerFunction");
await Task.Delay(3000);
// throw new InvalidOperationException("Fail");
//throw new InvalidProgramException("Fail");
Console.WriteLine($"{arg2} from AsyncWorkerFunction");
return arg1;
}
public async Task AsyncWorkerAction(int arg1, string arg2) {
Console.WriteLine("Entering AsyncWorkerAction");
await Task.Delay(3000);
//throw new InvalidOperationException("Fail");
//throw new InvalidProgramException("Fail");
Console.WriteLine($"{arg2} from AsyncWorkerAction");
Console.WriteLine($"Valor de AsyncWorkerAction: {arg1}");
}
public void WorkerAction(int arg1, string arg2) {
Console.WriteLine("Entering WorkerAction");
Thread.Sleep(3000);
// throw new InvalidOperationException("Fail");
// throw new InvalidProgramException("Fail");
Console.WriteLine($"{arg2} from WorkerAction");
Console.WriteLine($"Valor de WorkerAction: {arg1}");
}
public int WorkerFuntion(int arg1, string arg2) {
Console.WriteLine("Entering WorkerFunction");
Thread.Sleep(3000);
//throw new InvalidOperationException("Fail");
//throw new InvalidProgramException("Fail");
Console.WriteLine($"{arg2} from WorkerFunction");
return arg1;
}
}
public class Retryer {
private readonly ushort numRetries;
private readonly uint delayMs;
private CancellationToken ct;
private Func<int, Exception, bool> shouldRetry;
public Retryer(ushort numRetries, uint delayMs, IEnumerable<Type> exHandlers) {
this.numRetries = numRetries;
this.delayMs = delayMs;
exHandlers = exHandlers?.AsParallel();
validateExHandles(exHandlers);
this.shouldRetry = GetShouldRetryFnc(numRetries, exHandlers);
}
public static async Task<T> Implementation<T>(Func<Task<T>> asyncFnc, ushort numRetries, uint delay, Func<int, Exception, bool> shouldRetry, CancellationToken ct = default) {
ct.ThrowIfCancellationRequested();
List<Exception> throwableEx = null;
for (int i = 0; i <= numRetries - 1; i++) {
try {
return await asyncFnc().ConfigureAwait(false);
}
catch (Exception ex) {
throwableEx = throwableEx ?? new List<Exception>();
throwableEx.Add(ex);
if (!shouldRetry(i, ex))
break;
await Task.Delay(TimeSpan.FromMilliseconds(delay), ct).ConfigureAwait(false);
}
}
throw new AggregateException(throwableEx).Flatten();
}
private static void validateExHandles(IEnumerable<Type> exceptions) {
bool? hasInvalidType = exceptions?.Any(type => !type.IsSubclassOf(typeof(System.Exception)));
if ((hasInvalidType.GetValueOrDefault()))
throw new ArgumentException("List of exception to handle contains one or more type not subclassing System.Exception", nameof(exceptions));
}
public static Task Retry(Action action, ushort numRetries, uint delay, IEnumerable<Type> exHandles, CancellationToken ct = default) {
Func<Task<bool>> toRun = ToAsycFnc(action);
return Retry(toRun, numRetries, delay, exHandles, ct);
}
public static Task RetryAsync(Action action, ushort numRetries, uint delay, IEnumerable<Type> exHandles, CancellationToken ct = default) {
Func<Task<bool>> toRun = ToAsycFnc(action);
Func<Task<bool>> toRun2 = () => Task.Run(toRun);
return Retry(toRun2, numRetries, delay, exHandles, ct);
}
public static Task Retry(Func<Task> asyncAction, ushort numRetries, uint delay, IEnumerable<Type> exHandles, CancellationToken ct = default) {
Func<Task<bool>> toRun = ToAsycFnc(asyncAction);
return Retry(toRun, numRetries, delay, exHandles, ct);
}
public static Task<T> Retry<T>(Func<T> fnc, ushort numRetries, uint delay, IEnumerable<Type> exHandlers, CancellationToken ct = default) {
Func<Task<T>> toRun = ToAsycFnc(fnc);
return Retry(toRun, numRetries, delay, exHandlers, ct);
}
public static Task<T> RetryAsync<T>(Func<T> fnc, ushort numRetries, uint delay, IEnumerable<Type> exHandlers, CancellationToken ct = default) {
Func<Task<T>> toRun = ToAsycFnc(fnc);
Func<Task<T>> toRun2 = () => Task.Run(toRun);
return Retry(toRun2, numRetries, delay, exHandlers, ct);
}
public static Task<T> Retry<T>(Func<Task<T>> asyncFnc, ushort numRetries, uint delay, IEnumerable<Type> exHandlers, CancellationToken ct = default) {
var exceptions = exHandlers?.AsParallel();
validateExHandles(exceptions);
var shouldRetry = GetShouldRetryFnc(numRetries, exceptions);
return Implementation(asyncFnc, numRetries, delay, shouldRetry, ct);
}
public Task Retry(Action action) {
Func<Task<bool>> toRun = ToAsycFnc(action);
return Retry(toRun);
}
public Task RetryAsync(Action action) {
Func<Task<bool>> toRun = ToAsycFnc(action);
Func<Task<bool>> toRun2 = () => Task.Run(toRun);
return Retry(toRun2);
}
public Task Retry(Func<Task> asyncAction) {
Func<Task<bool>> toRun = ToAsycFnc(asyncAction);
return Retry(toRun);
}
public Task<T> Retry<T>(Func<T> fnc) {
Func<Task<T>> toRun = ToAsycFnc(fnc);
return Retry(toRun);
}
public Task<T> RetryAsync<T>(Func<T> fnc) {
Func<Task<T>> toRun = ToAsycFnc(fnc);
Func<Task<T>> toRun2 = () => Task.Run(toRun);
return Retry(toRun2);
}
public Task<T> Retry<T>(Func<Task<T>> asyncFnc) {
return Implementation(asyncFnc, this.numRetries, this.delayMs, this.shouldRetry, this.ct);
}
private static Func<Task<bool>> ToAsycFnc(Action action) {
return () =>
{
action();
return Task.FromResult(true);
};
}
private static Func<Task<bool>> ToAsycFnc(Func<Task> asyncAction) {
return async () =>
{
await asyncAction().ConfigureAwait(false);
return true;
};
}
private static Func<Task<T>> ToAsycFnc<T>(Func<T> fnc) {
return () => Task.FromResult(fnc());
}
private static Func<int, Exception, bool> GetShouldRetryFnc(ushort numRetries, IEnumerable<Type> exHandles) {
bool? shouldHandle(Exception ex) => exHandles?.Any(t => t.IsInstanceOfType(ex));
return (int i, Exception ex) => (i < numRetries) && shouldHandle(ex).GetValueOrDefault();
}
}
}
</script><b>
UPDATE:</b> Según todas mis pruebas; es completamente seguro utilizar en la función llamada ConfigureAwait(false) en caso de no necesitar ningún contexto que se pierda.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Resulta que según .NET maneja los contextos de sincronización, la función llamada (el Retryer en este caso) puede continuar su ejecución en otro hilo sin el contexto original y la función llamadora podría seguir con su contexto omitiendo ConfigureAwait. También pasaría lo mismo en el código interno de la función que queremos reintentar. Si ésta necesita un contexto que se pierde puede omitir ConfigureAwait para recapturar el contexto después del await aunque el Retryer use ConfigureAwait(false). En este último caso no se ganaría mucho en cuestión de rendimiento pero, como es algo que no podemos evitar, independiza la implementación del Retryer de la necesidad de contexto de su llamador y de lo que llama.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<b>UPDATE2:</b> El token de cancelación pasado al retryer puede estar o no asociado a la tarea interna que el retryer ejecuta. Si están asociados; la cancelación del la tarea interna cancela el bucle de reintentos y la espera. Si no están asociados, se podría cancelar la tarea interna e indicar al retryer que siga intentándolo. También se podría cancelar el bucle del retryer y su espera sin cancelar la tarea interna (que podría cancelarse por su propio timeout, por ejemplo).</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Esto nos proporciona una flexibilidad bastante buena y nos permite abarcar la mayoría de las situaciones que debe cumplir el sistema.</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-62035460912780950472019-08-19T02:36:00.001-07:002021-06-07T23:18:00.605-07:00Pillándole el rollo a Command Query Responsibility Segregation (CQRS) de una vez por todas.<div style="text-align: justify;">
Hay que ver lo que se complica la gente con conceptos tan sencillotes (aunque increiblemente útiles). <a href="http://altplusf13.blogspot.com/2018/08/hidratar-agregado-con-event-sourcing.html">La ultima vez que hablé sobre CQRS</a> con alguien; este alguien tenía un pifostio montado en la cabeza sobre event sourcing, read/write models, consistencia eventual y un buen montón de paranoias que creía que era en lo que consistía CQRS.</div>
<div style="text-align: justify;">
<br /></div>
<a name='more'></a><div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
La realidad es que el concepto es muchísimo más sencillo que eso: CQRS es simplemente una idea que se desea honrar en la arquitectura (ya sea de software, de infraestructura, de contextualización, de comunicaciones, etc) de nuestro sistema. Esa idea es separar de forma muy explícita los componentes (de software, de infraestructura, etc) que gestionan un comando, con el potencial de provocar algún cambio de estado en el sistema, de los componentes que gestionan una consulta al sistema.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Y ya está. No es más que eso. Todo lo demás (ES, RW model, etc) son constuctos utilizados para honrar esa idea en diferentes capas del sistema (y para facilitar la extensión de CQRS a las subsiguientes capas) de una forma que nos permite sacarle todo el jugo a CQRS.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Repasemos un supuesto muy, pero que muy, básico:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
En el código puro y duro; honrar a CQRS supone tener elementos y sus dependencias (no voy a decir clases porque no tiene por que ser OOP) muy claramente diferenciados para gestionar los comandos y las consultas. Aparte de las ventajas inherentes de esa arquitectura de software a nivel de programación; hacer esto nos permite extender mas facilmente CQRS al siguiente nivel.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Este siguiente nivel consiste en poder segregar las máquinas que ejecutan el código que gestiona los comandos y el que realiza las consultas. Si pensamos en los típicos servidores web; gracias a la arquitectura del paso anterior; podemos desplegar el código de gestión de comandos en un servidor (o en varios) y el código que realiza las consultas en otro (u otros). Aquí nos apoyamos en elementos como balanceadores, proxies inversos, servicios de descubrimiento, servidores de sesión y estado, etc para poder realizar la segregación y, de paso, ganar escalabilidad horizontal y redimiento.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
En el último nivel (ya dije que el ejemplo iba a ser muy muy básico) tenemos la peristencia; la cual también podemos dividir entre la máquina que recibe los cambios a persistir producidos por un comando y la máquina que realiza las consultas del estado del sistema. Nos apoyamos en elementos como Read/Write model, Event Sourcing, etc para poder llevar a cabo esta segregación. La escalabilidad horizontal del Read Model es trivial y el rendimiento en un Read Model desnormalizado es muy alto.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Así que sí, como me pasó a mi una vez, alguien te pregunta alguna vez sobre las ventajas de CQRS así sin más; lo único que se puede responder, ante la falta de más contexto es: especialización y simplicidad; puesto que lo demás depende del contexto en el que se aplique. Si al entrevistador no le gusta la respuesta porque piensa que es insuficiente (si quiere más detalles tiene que darme contexto) o él mismo lo completa con cosas como escalabilidad, rendimiento, descentralización, etc; es que el propio entrevistador tiene una idea, quizás no equivocada, pero demasiado enrevesada, innecesaria y compleja; lo que significa que no tiene, de verdad, del todo claro la idea en sí y solo sabe de implementación de esa idea en todas las capas como un todo indivisible.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Esa visión es una versión un poco más avanzada de <a href="https://www.adictosaltrabajo.com/2012/01/30/prohibidos-monos-lagartos/">Mono-Lagarto</a> que flipa un poco con buzzwords y al menos le echa algo de tiempo en leer sobre ellas.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Como decía mi abuela: "En el país de los Mono-Lagartos el buzzworder es el rey"</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-60060142522638220972019-06-05T05:30:00.000-07:002019-06-20T05:18:21.529-07:00Que las promesas no te controlen a ti.<div style="text-align: justify;">
Pues sí; aquí estamos tirando código Javascript que tenga que funcionar en navegadores relativamente antiguos y sin que me dejen usar <a href="https://babeljs.io/">Babel</a> debido al gañanismo atávico de Españistan.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Aquí estamos usando la implementación de las promesas provistas por JQuery que deja mucho que desear. Pero a lo tonto he interiorizado varias cosas interesantes que quiero compartir.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<a name='more'></a><div style="text-align: justify;">
<br /></div>
<h3 style="text-align: justify;">
Encadenar outputs personalizados</h3>
<div style="text-align: justify;">
Cuando uno se crea sus propias promesas es fácil utilizar una estructura que se vaya pasando por todas las promesas encadenadas usando los parámetros de <i>resolve([args])</i> y <i>reject([args])</i> pero cuando utilizamos las promesas que retorna el framework nos encontramos que se nos fastidia la cadena de respuesta.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Por ejemplo, la función <i>$.ajax</i> retorna una promesa con los datos de la respuesta <i>xmlhttprequest</i> y si la retornamos directamente podemos controlar que la promesa ha sido resuelta o rechazada pero perderemos la cadena de información que queríamos mantener.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Pongamos el siguiente ejemplo:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<b>function getDato1Promise(){</b></div>
<div style="text-align: justify;">
<div>
<b>return $.ajax({</b></div>
<div>
<b> method: "POST",</b></div>
<div>
<b> dataType: "json",</b></div>
<div>
<b> url: APIurl,</b></div>
<div>
<b> });</b></div>
</div>
<div style="text-align: justify;">
<b>}</b></div>
<div style="text-align: justify;">
<b><br /></b></div>
<div style="text-align: justify;">
<div>
<b>function getDato2Promise(dato1){</b></div>
<div>
<div>
<b>return $.ajax({</b></div>
<div>
<b> method: "POST",</b></div>
<div>
<b> dataType: "json",</b></div>
<div>
<b> url: APIurl,</b></div>
<div>
<b> data: {</b></div>
<div>
<b> dato1: dato1</b></div>
<div>
<b> }</b></div>
<div>
<b> });</b></div>
</div>
<div>
<b>}</b></div>
</div>
<div style="text-align: justify;">
<b><br /></b></div>
<div style="text-align: justify;">
<div>
<div>
<b>function getDato3Promise(dato1, dato2){</b></div>
<div>
<div>
<b>return $.ajax({</b></div>
<div>
<b> method: "POST",</b></div>
<div>
<b> dataType: "json",</b></div>
<div>
<b> url: APIurl,</b></div>
<div>
<b> data: {</b></div>
<div>
<b> dato1: dato1,</b></div>
<div>
<b> dato2: dato2</b></div>
<div>
<b> }</b></div>
<div>
<b> });</b></div>
</div>
<div>
<b>}</b></div>
</div>
<div>
<b><br /></b></div>
</div>
<div style="text-align: justify;">
<b>getDato1Promise().done(getDato2Promise).done(getDato3Promise)</b></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<i>getDato1Promise</i> obtiene el dato1 y se lo pasa por <i>resolve(dato1)</i> a <i>getDato2Promise</i>; este último usa dato1 para obtener el dato2 y todo parece que va bien hasta que nos damos cuenta que necesitamos dato1 y dato2 para que la promesa <i>getDato3Promise</i> funcione. Desgraciadamente a la última promesa sólo le llega el resultado de <i>getDato2Promise</i> que es dato2.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Para solucionar esto tenemos que controlar el <i>resolve </i>y <i>reject</i> de la promesa que retornamos y no delegar en la que utiliza <i>$.ajax</i>. La mejor manera que he encontrado es crear y retornar una promesa propia; la cual se resolverá o rechazará según la respuesta de los callbacks de <i>$.ajax.</i></div>
<div style="text-align: justify;">
<i><br /></i></div>
<div style="text-align: justify;">
<b>function getDato1Promise() {</b></div>
<div style="text-align: justify;">
<div>
<b><br /></b></div>
<div>
<b> var estructura = {};</b></div>
<div>
<b><br /></b></div>
<div>
<b> var df = $.Deferred();</b></div>
<div>
<b> </b></div>
<div>
<b> $.ajax({</b></div>
<div>
<b> method: "POST",</b></div>
<div>
<b> dataType: "</b><b>json</b><b>",</b></div>
<div>
<b> url: APIurl,</b></div>
<div>
<b> success: function (data) {</b></div>
<div>
<b> estructura.dato1 = data;</b></div>
<div>
<b> df.resolve(estructura);</b></div>
<div>
<b> },</b></div>
<div>
<b> error: function (jqXHR, textStatus, errorThrown) {</b></div>
<div>
<b> df.reject();</b></div>
<div>
<b> }</b></div>
<div>
<b> });</b></div>
<div>
<b><br /></b></div>
<div>
<b> return df.promise();</b></div>
<div>
<b><br /></b></div>
<div>
<b>}</b></div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<div>
<b>function getDato2Promise(estructura) {</b></div>
<div>
<div>
<b><br /></b></div>
<div>
<b> var df = $.Deferred();</b></div>
<div>
<b> </b></div>
<div>
<b> $.ajax({</b></div>
<div>
<b> method: "POST",</b></div>
<div>
<b> dataType: "</b><b>json</b><b>",</b></div>
<div>
<b> url: APIurl,</b></div>
<div>
<div>
<b> data: {</b></div>
<div>
<b> dato1</b><b>: estructura.dato1</b></div>
<div>
<b> },</b></div>
</div>
<div>
<b> success: function (data) {</b></div>
<div>
<b> estructura.dato2= data;</b></div>
<div>
<b> df.resolve(estructura);</b></div>
<div>
<b> },</b></div>
<div>
<b> error: function (jqXHR, textStatus, errorThrown) {</b></div>
<div>
<b> df.reject();</b></div>
<div>
<b> }</b></div>
<div>
<b> });</b></div>
<div>
<b><br /></b></div>
<div>
<b> return df.promise();</b></div>
<div>
<b><br /></b></div>
<div>
<b>}</b></div>
</div>
</div>
<div style="text-align: justify;">
<b><br /></b></div>
<div style="text-align: justify;">
<div>
<b>function getDato3Promise(estructura) {</b></div>
<div>
<div>
<b><br /></b></div>
<div>
<b> var df = $.Deferred();</b></div>
<div>
<b> </b></div>
<div>
<b> $.ajax({</b></div>
<div>
<b> method: "POST",</b></div>
<div>
<b> dataType: "</b><b>json</b><b>",</b></div>
<div>
<b> url: APIurl,</b></div>
<div>
<div>
<b> data: {</b></div>
<div>
<b> dato1</b><b>: estructura.dato1,</b></div>
<div>
<b> dato2: estructura.dato2</b></div>
<div>
<b> },</b></div>
</div>
<div>
<b> success: function (data) {</b></div>
<div>
<b> estructura.dato3= data;</b></div>
<div>
<b> df.resolve(estructura);</b></div>
<div>
<b> },</b></div>
<div>
<b> error: function (jqXHR, textStatus, errorThrown) {</b></div>
<div>
<b> df.reject();</b></div>
<div>
<b> }</b></div>
<div>
<b> });</b></div>
<div>
<b><br /></b></div>
<div>
<b> return df.promise();</b></div>
<div>
<b><br /></b></div>
<div>
<b>}</b></div>
</div>
</div>
<div style="text-align: justify;">
<b><br /></b></div>
<div style="text-align: justify;">
<b>getDato1Promise().done(getDato2Promise).done(getDato3Promise).done(()=>console.dir(estructura));</b></div>
<div style="text-align: justify;">
<br /></div>
<h3 style="text-align: justify;">
Ejecutar promesas de forma secuencial</h3>
<div style="text-align: justify;">
Esta necesidad es rara, muy muy rara; quizás para algo de throttling en ejecuciones asíncronas podría ser útil. En mi caso es la interacción con un programa externo que se lanza por la típica asociación de protocolo a un programa específico (<span style="background-color: #eff0f1; color: #242729; font-family: "consolas" , "menlo" , "monaco" , "lucida console" , "liberation mono" , "dejavu sans mono" , "bitstream vera sans mono" , "courier new" , monospace , sans-serif; font-size: 13px; font-style: inherit; font-weight: inherit; white-space: inherit;">UrlAssociations</span>). Si voy demasiado rápido y lanzo las tareas asíncronas en paralelo el programa se queda frito.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Supongamos que queremos lanzar de forma secuencial tareas asíncronas que retornan una promesa por cada uno de los valores contenidos en un array (que puede ser variable).</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<b>var params = [1,2,3,4,5,6];</b></div>
<div style="text-align: justify;">
<b><br /></b></div>
<div style="text-align: justify;">
<div>
<b>function doSomethingAsync(param){</b></div>
<div>
<div>
<b>return $.ajax({</b></div>
<div>
<b> method: "POST",</b></div>
<div>
<b> dataType: "json",</b></div>
<div>
<b> url: APIurl,</b></div>
<div>
<b> data: {</b></div>
<div>
<b> </b><b>param</b><b>: </b><b>param</b></div>
<div>
<b> }</b></div>
<div>
<b> });</b></div>
</div>
<div>
<b>}</b></div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Si recorremos el array y llamamos a la función asíncrona por cada valor las estaríamos ejecutando en paralelo:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<div>
<b>params.forEach(function(element) {</b></div>
<div>
<b> doSomethingAsync(element);</b></div>
<div>
<b>});</b></div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Si intentamos encadenarlas tampoco funciona porque al pasarles el parámetro las estamos lanzando:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<b>var start = $.when();</b></div>
<div style="text-align: justify;">
<div>
<div>
<b>params.forEach(function(element) {</b></div>
<div>
<b> start = start.then(doSomethingAsync(element));</b></div>
<div>
<b>});</b></div>
</div>
</div>
<div style="text-align: justify;">
<b><br /></b></div>
<div style="text-align: justify;">
La solución se basa en crear una funciona anónima enlazada con el elemento que le toque y utilizar esa función como callback para el <i>then:</i></div>
<div style="text-align: justify;">
<i><br /></i></div>
<div style="text-align: justify;">
<div>
<b>var start = $.when(); </b>//comenzamos con una promesa ya resuelta</div>
<div>
<div>
<b>params.forEach(function(element) {</b></div>
<div>
<b> start = start.then(() => doSomethingAsync(element));</b></div>
<div>
<b>});</b></div>
</div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Así no estamos ejecutando la función <i>doSomethingAsync</i>, si no que estamos creando una función que ejecuta <i>doSomethingAsync </i>cuando es llamada como callback del <i>then; </i>que es cuando la promesa anterior ya ha sido resuelta y por tanto habrá terminado.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
El programador avispado ya se habrá dado cuenta de que el patrón que aquí se presenta; el de agregar el resutado a la misma variable y seguir con el siguiente calculo; es lo que hace la función <i>reduce() </i>de un array. Guay, vamos a usarlo para que quede más cuco:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<div>
<b>params.reduce((previousPromise, element) => {</b></div>
<div>
<b> return previousPromise.then(() => doSomethingAsync(element));</b></div>
<div>
<b> }</b></div>
<div>
<b> , $.when());</b></div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Y he terminado por hoy. Espero que os haya gustado y os sea útil de alguna manera.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Happy coding bastardillos!!!</div>
Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-6168465570663215053.post-43964692601186860342019-05-17T03:05:00.000-07:002019-06-20T05:22:40.506-07:00Precomputa sin miedo que no muerde.<div style="text-align: justify;">
Parece ser que, en el mundo de la gente que intenta aplicar DDD, <a href="http://altplusf13.blogspot.com/2018/08/hidratar-agregado-con-event-sourcing.html">hay mucha reticencia a la hora de hacer cálculos en diferido fuera de la operación principal</a> (agregado raíz) que necesita los resultados de ese cálculo. Eso es un error que te empantana hasta las tetillas y te hace liarla muy parda.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<a name='more'></a><div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Pongamos un ejemplo sencillo que se complica cuando queremos tener agregados que realicen una operación en una única transacción (regla de oro de los agregados) que necesita ser confirmada por un montón de elementos; los cuales cada uno tiene sus datos propios que podrían provocar el rechazo de ese cambio.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Tenemos un Seminario con una lista de 1000 Participantes. El Seminario tiene un fecha de programa de comienzo y de fin. El seminario se puede reprogramar para otras fechas según unas reglas propias pertenecientes al Seminario y además sólo si cada uno de los Participantes puede aceptar esa nueva fecha según una serie de datos pertenecientes a ese Participante. En caso de que un solo Participante no pueda aceptar la fecha la reprogramación se cancela.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
A primera instancia; el instinto nos dice que recorramos los 1000 participantes y les preguntemos si pueden aceptar. Bueno venga, 1000 identificadores con algún dato que otro en memoria no es tanto pero ¿Y si fueran varios millones?</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
¿Recuerdas la entrada de <a href="https://altplusf13.blogspot.com/2015/05/dale-la-vuelta-antes-de-decidir.html">Dale la vuelta antes de decidir</a>? Pues vamos a aplicarla.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Lo que debemos hacer es acotar las fechas que puede aceptar el Seminario en unos campos en persistencia que se usarán para alimentar el agregado que reprograma el Seminario. A medida que, al trabajar con el sistema, asociemos Participantes al Seminario uno por uno, estos campos irán cogiendo los valores mas restrictivos de cada Participante que se añada y el conjunto de fechas de reprogramación aceptadas por el agregado se ira reduciendo. A la hora de desasociar un Participante hacemos lo contrario y ampliamos el conjunto de fechas aceptables por el Seminario. Esto provoca que el sistema esté siempre consistente a cada pasito (añadir un nuevo Participante en el Seminario) que damos y que luego podamos aplicar reglas del dominio sin necesidad de chequear varios millones de valores solo para una respuesta de un agregado raiz.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Ya sólo nos queda obtener el agregado que reprograma las fechas del Seminario; que entre su información tiene las fechas aceptables por el Seminario y lanzar la operación de reprogramación; pudiéndola rechazar o aceptar con un par de comparaciones simples.</div>
<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-51055067197379410872019-05-16T05:26:00.003-07:002019-06-20T05:13:26.889-07:00Las entrañas del JPEGEstupenda explicación interactiva de la compresión JPEG:<br />
<br />
<a href="https://parametric.press/issue-01/unraveling-the-jpeg/">https://parametric.press/issue-01/unraveling-the-jpeg/</a>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-19619276812164094362019-04-29T04:06:00.003-07:002021-06-07T23:25:18.091-07:00Que no Neo; que no hay cuchara.<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgH3bLarnqvS6YD-O1pZXV3xqWbA1ntCUsJv_Il_EofkzEonXF6AblXE7NXY7eqCiy09RVJM2LopcDvFLNWKa1j6-2i78WnzlRWHFRAjq8f8NXT8ULHRpiMGpOmieAD2REet7D4FvI0BQA/s1600/cuchara.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="159" data-original-width="316" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgH3bLarnqvS6YD-O1pZXV3xqWbA1ntCUsJv_Il_EofkzEonXF6AblXE7NXY7eqCiy09RVJM2LopcDvFLNWKa1j6-2i78WnzlRWHFRAjq8f8NXT8ULHRpiMGpOmieAD2REet7D4FvI0BQA/s1600/cuchara.jpg" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
En DDD las entidades no existen. ¡BUM! Toma locura gorda; contradiciendo todos los textos existentes sobre el tema... o no ;-)</div>
<div style="text-align: justify;">
<br />
<a name='more'></a><br /></div>
<div style="text-align: justify;">
Ahora que tengo tu atención y antes de que escribas una diatriba pedante en los comentarios; poniéndome a caer de un burro; voy a extenderme para dar una explicación lógica a esto.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
A lo que me refiero arriba es que en DDD; con un acercamiento y enfoque más moderno que los textos (con todos mis respetos; no les quito el gran merito que les merece; pero están anticuados y anclados a una visión OOP que genera una gran cantidad de artefactos indeseados) de Eric Evans, Vaughn Vernon y Martin Fowler; las entidades no se deben modelar como elementos reutilizables, representando la vida real, con los que trabajar por parte de los agregados y la capa de servicio de aplicación.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Probemos con una entidad Customer creada para ser reutilizada por todo el sistema. Pronto nos daremos cuenta que esta entidad esta incluyendo información y métodos para el sistema de ventas, de marketing, de facturación, de reviews, etc. Feo, no nos vale.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Pasamos a crear una entidad Customer por cada Bounded Context (BC). También, muy pronto, nos encontraremos que para ciertas operaciones, aunque sean del mismo BC, no necesitamos ni la mitad de la información que contiene el Customer. Feo, no nos vale tampoco.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
¿Que hacemos entonces?</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Cada agregado debe tener la información del Customer justa y necesaria para aplicar las reglas de dominio e invariantes. Aquí, Customer como entidad no tiene por qué ser una plantilla de datos (ni clase, ni estructura, ni nada); eso son detalles de implementación; aquí, cada Customer de cada agregado es una entidad como Rol asignado; un rol conocido por el agregado, que sabe que si la acción que realiza resulta en un cambio de un atributo del Customer, entonces como información a persistir debe retornar el nuevo valor del atributo y el Identificador único del Customer.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Las entidades son elementos internos a cada agregado. No existen fuera del agregado. No son una plantilla de datos para usar en la capa de aplicación. No son elementos referenciables desde ningún sitio fuera de su propio agregado y son únicas (ver más abajo) para cada agregado.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Obviamente se terminaran teniendo agregados que con la misma información puedan aplicar varias acciones o estructura de datos que representen parte del Customer que pueden ser comunes para varios agregados; por lo que al final terminaremos creando plantillas que representan a una entidad Customer para ciertas acciones del dominio; pero estas no se utilizarán más que para que el agregado cree/obtenga una referencia a ella e internamente trabaje con ella; y tendremos varias. Esta es la parte de los detalles de la implementación que requiere un buen diseño OOP y buenas prácticas SOLID.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Para el diseño del sistema utilizando DDD (detalles de implementación aparte) lo importante es darse cuenta de que las entidades, como elemento de representación de un concepto de la vida real para todo el sistema (o BC), no existen.</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-15468831768145339152019-02-13T04:50:00.002-08:002019-03-12T00:54:20.485-07:00Las montañas de la locura.<div style="text-align: justify;">
Si quieres hacer un juego donde las localizaciones son teseractos de esos que tienen geometrías no euclidianas como en el relato de Lovecraft o quieres hacer uno en el que te drogas mucho, tienes que usar este engine tan guapo:</div>
<div style="text-align: justify;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/kEB11PQ9Eo8/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/kEB11PQ9Eo8?feature=player_embedded" width="320"></iframe></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Y aquí el código fuente que supongo que también seguirá estructuras de control no algebraicas para que ya pierdas la poca cordura que te queda:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<a href="https://github.com/HackerPoet/NonEuclidean">https://github.com/HackerPoet/NonEuclidean</a></div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-50070633295445216472018-12-20T03:19:00.002-08:002018-12-27T00:23:59.442-08:00Consideraciones para multihilo<div style="text-align: justify;">
Un buen post para adentrarse en el mundo del multihilo sin liarla parda:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<a href="https://www.acodersjourney.com/top-20-cplusplus-multithreading-mistakes/">https://www.acodersjourney.com/top-20-cplusplus-multithreading-mistakes/</a></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Bueno... asumámoslo, la vas a liar parda de todas maneras pero al menos tienes un buen comienzo para averiguar la que has liado y cómo arreglarlo. ;-)</div>
Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-6168465570663215053.post-25792313922344571442018-12-12T03:20:00.000-08:002018-12-27T00:22:39.468-08:00The big idea is messaging<div style="text-align: justify;">
Esta es la continuación de la <a href="http://altplusf13.blogspot.com/2018/12/los-agregados-no-son-elementos.html">entrada enterior.</a></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Pues sí, mensajes; el concepto más importante de la P.O.O. y el más ignorado y olvidado tanto por desarrolladores como por los diseñadores de lenguajes de programación O.O. Y eso es una pena porque los mensajes aportan un cosa imprescindible: <b>Contexto</b>. Y ese contexto es lo que nos permite diseñar agregados que no tengan que ser elementos persistibles y que no filtren detalles de su implementación.</div>
<div style="text-align: justify;">
<br /></div>
<a name='more'></a><div style="text-align: justify;">
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
El primer mensaje es bastante obvio; lo que recibimos de la interfaz de usuario o de otro sistema es un Comando; el propio comando indica qué se solicita hacer y la información que contiene el comando nos indica con qué y en qué se solicita.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Partimos del desproposito anterior:</div>
<div style="text-align: justify;">
<br /></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;">public class ApplicationServices.MyService{</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"> public void DoSomething(...){</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"> MyAggregate agg = myAggRepository.Get(aggId);</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"> agg.DoSomething(...); //cambia su estado interno con el nuevo estado del sistema</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"> myAggRepository.Save(agg); //coge su estado interno y lo persiste</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"> }</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;">}</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: inherit;">Lo modificamos para que acepte un comando:</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: inherit;"><br /></span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;">public class ApplicationServices.MyService{</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"> public void DoSomething(myCommand command)</span></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<span style="font-family: inherit;">Este comando (simple </span><a href="https://docs.microsoft.com/en-us/previous-versions/msp-n-p/ff649585(v=pandp.10)" style="font-family: inherit;">DTO</a><span style="font-family: inherit;">) debe contener toda la </span>información<span style="font-family: inherit;"> necesaria para aplicar la </span>operación<span style="font-family: inherit;"> del servicio.</span></div>
<div style="text-align: justify;">
<span style="font-family: inherit;"><br /></span>
Ya tenemos un mensaje con contexto: nuestro sistema sabe exáctamente que acción se ha solicitado independientemente de en qué función y/o objeto nos encontremos. Podemos modelar el repositorio para que monte el agregado necesario para esa operación sin que el servicio tenga que usar explícitamente una función del repositorio ni el repositorio tenga que "saber" que está siendo usado por la función "DoSomething".</div>
<div style="text-align: justify;">
<br /></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;">public class ApplicationServices.MyService{</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"> public void DoSomething(myCommand command){</span></div>
<div style="background-color: white; text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div style="text-align: justify;">
<span style="font-family: "courier new" , "courier" , monospace;"> </span><span style="background-color: white; font-family: "courier new" , "courier" , monospace; text-align: justify;">MyAggregate agg = myAggRepository.Handle(command);</span></div>
<div style="text-align: justify;">
}</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
}</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Lo mismo podemos hacer con el agregado. Modifiquémoslo para que gestione el comando:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;">public class ApplicationServices.MyService{</span></div>
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;"> public void DoSomething(myCommand command){</span></div>
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> I</span><span style="background-color: white; font-family: "courier new" , "courier" , monospace; text-align: justify;">MyAggregate agg = myAggRepository.Handle(command);</span></div>
<div>
<span style="background-color: white; font-family: "courier new" , "courier" , monospace; text-align: justify;"> agg.Handle(command);</span></div>
<div>
}</div>
<div>
<br /></div>
<div>
}</div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Y ahora viene l<span style="font-family: inherit;">a pieza gorda de puzle que hace que todo encaje de una forma tan bonita que no tengo palabra</span><span style="font-family: inherit;">s. Vamos a hacer el agregado </span><b style="font-family: inherit;">inmutable</b><span style="font-family: inherit;">; el estado interno del agregado no cambia. Ese estado interno sólo se usa para comprobar las invariantes del dominio y aplicar las reglas y los </span>cálculos<span style="font-family: inherit;"> de los cambios. Los cambios sufridos en el dominio </span>serán<span style="font-family: inherit;"> retornados por el agregado en forma de otro <b>mensaje </b>que en este caso lo vamos a contextualizar como un evento de un hecho pasado.</span></div>
<div style="text-align: justify;">
<span style="font-family: inherit;"><br /></span></div>
<div style="text-align: justify;">
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;">public class ApplicationServices.MyService{</span></div>
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;"> public void DoSomething(myCommand command){</span></div>
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> I</span><span style="background-color: white; font-family: "courier new" , "courier" , monospace; text-align: justify;">MyAggregate agg = myAggRepository.Handle(command);</span></div>
<div>
<span style="background-color: white; font-family: "courier new" , "courier" , monospace; text-align: justify;"> SomethingHanppened myEvent = agg.Handle(command);</span></div>
<div>
}</div>
<div>
<br /></div>
<div>
}</div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
En resumen, se expresan los cambios sufridos en el dominio en forma de <b>evento</b>.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Este evento se utilizará para persistir los cambios en BD y para notificar ese cambio a los demás componentes del sistema interesados.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;">public class ApplicationServices.MyService{</span></div>
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;"> public void DoSomething(myCommand command){</span></div>
<div style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> I</span><span style="background-color: white; font-family: "courier new" , "courier" , monospace; text-align: justify;">MyAggregate agg = myAggRepository.Handle(command);</span></div>
<div>
<span style="background-color: white; font-family: "courier new" , "courier" , monospace; text-align: justify;"> SomethingHanppened myEvent = agg.Handle(command);</span></div>
<div>
<span style="background-color: white; font-family: "courier new" , "courier" , monospace; text-align: justify;"> IPersistence.Handle(myEvent);</span></div>
<div>
<span style="background-color: white; font-family: "courier new" , "courier" , monospace; text-align: justify;"> IMessageBus.Handle(myEvent);</span></div>
<div>
}</div>
<div>
<br /></div>
<div>
}</div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Como la capa de persistencia (<i>IPersistence</i>) tiene una implementación concreta para <i>Handle(SomethingHappened)</i>; esta sabe perfectamente qué tiene que hacer para persistir los cambios puesto que sabe lo que está pasando, tiene mensaje, tiene <b>contexto</b>. Puede tener que actualizar 2 campos de una tabla, otro campo de otra, una inserción de una fila entera en una tercera tabla, etc. Un campo puede necesitar ser actualizado aunque no sea necesaria su lectura primeramente para que el agregado realice su trabajo y viceversa; un campo puede necesitar ser leído para que el agregado haga su trabajo pero no es necesario persistirlo porque esa operación nunca cambia el valor de ese campo. </div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Hemos independizado y quitado paja del agregado y de la capa de persistencia puesto que el agregado puede retornar eventos con "campos" a persistir que ni siquiera necesita tener en su estado interno (van a cambiar sí o sí al aplicar la operación, independientemente del valor que contengan) y el agregado puede tener "campos" en su estado interno que no es necesario persistir (sólo se leyeron para aplicar las invariantes del dominio y/o en cálculos de los cambios) y son, por lo tanto, innecesarios para la capa de persistencia.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Hemos eliminado el desajuste por impedancia existente entre el estado de un agregado y la persistencia de los cambios.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Dejo para otra entrada posterior los detalles de las tripas de un agregado inmutable.<br />
<br />
PD: ¿Os habéis fijado lo que se parecen los paradigmas O.O. y funcionales siempre y cuando se haga bien el diseño O.O.? La guerra no debería ser "O.O. vs Func"; debería ser simplemente "no hagas mierdacas en O.O. que te astillo. ¡Gañan!"<br />
<br />
<u>Edit 13/12/2018</u><br />
<u><br /></u>
Lectores de este blog me han comentado en persona que eso de que todo se llame "Handle" no es buena idea porque pierdes la "pista" sobre qué está haciendo el objeto ya que la función no tiene un nombre especifico estilo "HazEstaAccionConEstosDatos". No estoy de acuerdo. Si tienes bien diseñada y segregada la responsabilidad <b>ÚNICA</b> del objeto no hay problemas. Centrémonos, por ejemplo, en el Repositorio. Su única responsabilidad es construir agregados a partir de los datos de persistencia y el input de entrada. La firma de la función especifica qué agregado retorna según el input de entrada; que, al ser un comando, nos indica explícitamente cual es la operación que queremos realizar; por lo que se convierte en treméndamente obvio lo que hace la función "Handle" de un repositorio.<br />
<br />
Es cuando tienes un mal diseño y <strike>mensajes</strike> churros de datos sin contexto cuando tienes que empezar a crear funciones con nombres increíblemente explícitos; ya que como el objeto no tiene una responsabilidad única y el <strike>mensaje</strike> churro de datos no significa ni representa nada concreto (falta de contexto) hay que darle un significado a la operación a través del nombre de la función.<br />
<br />
<br />
<u>Edit 26/12/2018</u><br />
<u><br /></u>
Otra razón por la que me gusta que las funciones se llamen <strike>"DoYourFuckinWorkMadaFacka"</strike> "Handle" es porque, si estamos intentando crear un sistema con abstracción de implementación y bajo acoplamiento, el llamador de la función no tiene que saber qué hace la función llamada; más allá de que retorna un tipo. Volvamos al Repositorio. Si llamamos a la función "ObtenerAgregadoPara(Comando)"; ¿Que hacemos si el Repositorio, ademas de obtener el Agregado, tiene que escribir en un Log o tiene que aplicar algún modelo de seguridad o de segregación multitenant? ¿Llamamos a la función ObtenerAgregadoConLogYSegregacionMultitenantPara(comando) o lo dejamos como está?<br />
<br />
Si cambiamos el nombre estamos filtrando detalles de la implementación y si dejamos el nombre actual estamos creando un sesgo cognitivo que nos "chirria" en la cabeza puesto que si, para escribir código decente, las funciones deberían hacer sólo una cosa y la función se llama "ObtenerAgregado" ¿Qué hace el Repositorio enredando con logs y con segregaciones multitenant?<br />
<br />
La función se llama "Handle" porque es justamente lo que hace: Manejar el comando coordinando una serie de funcionalidades y como es un Repositorio pues retorna un agregado. Fijaos que el retorno de la función, un Agregado, está más asociado a la responsabilidad única de la Clase que a la función en si.</div>
Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-6168465570663215053.post-22601906472906904332018-12-07T01:45:00.000-08:002018-12-11T02:38:54.531-08:00Los agregados no son elementos persistibles.<div style="text-align: justify;">
Si habéis leído ciertas entradas en este blog; alguna vez me habréis visto comentar que hacer que un agregado sea un elemento que se guarda en persistencia a bloque (y por ende se lee de persistencia a bloque) es un detalle de implementación que no es obligatorio y no debe afectar al diseño del agregado ni que este dependa del esquema de persistencia.</div>
<div style="text-align: justify;">
<br />
<a name='more'></a><br /></div>
<div style="text-align: justify;">
Pues bien: Ni pu** caso, ni detalle de implementación ni pitufos en vinagre. Directamente NO. No es un elemento persistible. Un agregado no es un puñado de "tablas de BD" representadas en memoria, y un agregado raíz no es añadir funciones públicas a una de esas "tablas". De hecho, un agregado raíz no tiene ni porqué tener identificador único.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<div>
Un agregado es un elemento que se encarga de comprobar las invariantes del dominio y aplicar las reglas de los cambios de ese mismo dominio.</div>
<div>
<br /></div>
<div>
Comprobar las invariantes del dominio significa: "¿Puedo hacer esta operación según el estado actual del sistema?" y para ello leemos de persistencia la información justa y necesaria para ello. Cogemos un campo de una tabla, dos campos de otra tabla, el número de entradas de una tercera, etc; con ello montamos el agregado y este se encarga comprobar que la operación es posible.</div>
<div>
<br /></div>
<div>
Aplicar las reglas de los cambios del dominio significa: "Ahora que ya sé que la operación es posible voy a realizar los cálculos necesarios para obtener el nuevo estado del sistema una vez realizada la operación". A veces es necesaria alguna información del sistema para realizar estos cálculos por lo que la información justa y necesaria para montar el agregado (lo expuesto en el punto anterior) se amplía para que abarque también estos datos.</div>
</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Ojo, que no estoy diciendo que para montar el agregado no se usen VO's y entidades. Claro que se usan. Esos datos dispersos de persistencia que se necesita leer se usan para crear VO's y entidades para alimentar el agregado y que trabaje con ellos.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Con elemento persistible me refiero a algo como esto:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
public class ApplicationServices.MyService{</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
public void DoSomething(...){</div>
<div style="text-align: justify;">
MyAggregate agg = myAggRepository.Get(aggId);</div>
<div style="text-align: justify;">
agg.DoSomething(...); //cambia su estado interno con el nuevo estado del sistema</div>
<div style="text-align: justify;">
myAggRepository.Save(agg); //coge su estado interno y lo persiste</div>
<div style="text-align: justify;">
}</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
}</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Al hacer esto te estás cargando todo el <a href="https://en.wikipedia.org/wiki/Domain-driven_design">DDD</a> y estás volviendo a hacer un sistema <a href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a>. Como el repositorio no sabe que carajo ha cambiado el agregado (se ha perdido la pretensión de la operación); lo único que puede hacer es obtener todo el estado interno del agregado y plantarlo en persistencia; incluida información que sólo se ha usado para comprobar las invariantes o realizar cálculos pero que no han cambiado después de la operación. Esto es igual al típico sistema CRUD donde la vista editable se pasa a piñón a persistencia porque no sabemos que quería hacer el usuario más allá de cambiar valores a campos que le presentamos en la vista.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Otra consecuencia perniciosa es que el agregado tiene que exponer en getter(s) su estado interno para que sea recuperado por el repositorio; el repositorio necesita ciertos conocimiento sobre como está implementado el agregado y el agregado los filtra. Nos hemos cargado de un plumazo la abstracción de la implementación, el bajo acoplamiento, la alta cohesión, el <a href="https://martinfowler.com/bliki/TellDontAsk.html">Tell Don't Ask</a> y yo qué sé cuántas cosas más.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Dejo para la siguiente entrada la respuesta a la pregunta <i>¿Y entonces que coj**** hago?</i> con una pista:</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<blockquote class="tr_bq">
<span style="background-color: cornsilk; color: #242729; font-family: "arial" , "helvetica neue" , "helvetica" , sans-serif; font-size: 15px;">“I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea. The big idea is messaging.” ~ Alan Kay</span></blockquote>
</div>
<div>
<br /></div>
Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-6168465570663215053.post-73888413944455142642018-11-30T02:46:00.002-08:002018-12-13T03:08:52.990-08:00Ambiente tóxico<div style="text-align: justify;">
<span style="font-family: "arial" , "helvetica" , sans-serif;">Voy a enumerar unas cuantas situaciones que desprenden tufo a lugar de trabajo tóxico; esto incluye management y compañeros de trabajo gañanes. Si más de uno de ellos te es familiar en el lugar donde estás trabajando ahora:</span></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<span style="font-family: "arial" , "helvetica" , sans-serif;"></span><br /></div>
<a name='more'></a><div style="text-align: justify;">
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
</div>
<div style="text-align: justify;">
<span style="font-family: "arial" , "helvetica" , sans-serif;"><br /></span>
</div>
<div class="separator" style="clear: both; text-align: justify;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDW6awQNl1hCU06RsJpnOdPyLrE6Sj6auGgc6Bydn3wCeKLnzN4HXE39M39AZVtLbHof-2wII2OhXg0hLd5LfaIsJoVqOyvVU_ol6R3YDWuX9VtomNGiAuKpLH8vpKYnfmYasDWxvEB78/s1600/gandalf.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: "arial" , "helvetica" , sans-serif;"><img border="0" data-original-height="384" data-original-width="400" height="307" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDW6awQNl1hCU06RsJpnOdPyLrE6Sj6auGgc6Bydn3wCeKLnzN4HXE39M39AZVtLbHof-2wII2OhXg0hLd5LfaIsJoVqOyvVU_ol6R3YDWuX9VtomNGiAuKpLH8vpKYnfmYasDWxvEB78/s320/gandalf.jpg" width="320" /></span></a></div>
<ul style="color: #0a0a0a; list-style-type: square; margin: 0.8em 0.7em 0.8em 1.9em; padding-left: 6px;">
<li style="margin: 0.3em auto; text-align: justify;"><div style="padding: 0px;">
<span style="font-family: "arial" , "helvetica" , sans-serif;">Siempre estás en un momento crítico. No hay tiempo para pararse a pensar y diseñar.</span></div>
</li>
<li style="margin: 0.3em auto; text-align: justify;"><div style="padding: 0px;">
<span style="font-family: "arial" , "helvetica" , sans-serif;">Todo es urgente. </span><span style="font-family: "arial" , "helvetica" , sans-serif;">Si todo es urgente entonces nada lo es de verdad. Las tareas y los proyectos no pueden ser priorizados (habría que tener tiempo para pensar...) La prioridad del día es cambiada constantemente por el manager que chille y llore más ese mismo día.</span></div>
</li>
<li style="margin: 0.3em auto; text-align: justify;"><div style="padding: 0px;">
<span style="font-family: "arial" , "helvetica" , sans-serif;">No hay presupuesto profesional. No hay formación activa, ni se compran recursos de formación pasiva. Tienes las mismas (o peores) herramientas de desarrollo que el administrativo que edita powerpoints y rellena columnas de excel.</span></div>
</li>
<li style="margin: 0.3em auto; text-align: justify;"><div style="padding: 0px;">
<span style="font-family: "arial" , "helvetica" , sans-serif;">No hay proceso de on-boarding. Nuevamente; planear un proceso de on-boarding para nuevas llegadas requiere pararse a pensar y tener las cosas organizadas.</span></div>
</li>
<li style="margin: 0.3em auto; text-align: justify;"><div style="padding: 0px;">
<span style="font-family: "arial" , "helvetica" , sans-serif;">No hay mentoring. Siéntate y haz las cosas a lo salvaje; saca las cosas como sea.</span></div>
</li>
<li style="margin: 0.3em auto; text-align: justify;"><div style="padding: 0px;">
<span style="font-family: "arial" , "helvetica" , sans-serif;">Sin documentación. Todo está en la cabeza de la gente y los silos de conocimiento se usan para mantener un pedacito de poder y convertirse en "indispensable".</span></div>
</li>
<li style="margin: 0.3em auto; text-align: justify;"><div style="padding: 0px;">
<span style="font-family: "arial" , "helvetica" , sans-serif;">Ningún reconocimiento por el trabajo bien hecho. El único trabajo bien hecho que se reconoce es estar quebrándose lo cuernos entre el despropósito creado por todo el ambiente tóxico y aun así sacar alguna mierdaca para adelante en el nido de víboras en el que te has metido.</span></div>
</li>
<li style="margin: 0.3em auto; text-align: justify;"><div style="padding: 0px;">
<span style="font-family: "arial" , "helvetica" , sans-serif;">Se ignoran los callejones sin salida y los terrenos pantanosos. Si golpeas la cabeza incontables veces en la pared del fondo del callejón sin salida alguna vez se abrirá un agujero. La mejor manera de salir de un terreno pantanoso es meterse cada vez mas adentro sin brújula y andando en círculos.</span></div>
</li>
<li style="margin: 0.3em auto;"><div style="padding: 0px; text-align: justify;">
<span style="font-family: "arial" , "helvetica" , sans-serif;">El más comprometido y que más esfuerzo dedica en hacer las cosas bien es un tipo conflictivo que es mejor evitar.</span></div>
</li>
</ul>
Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-6168465570663215053.post-91020286275983998532018-08-31T03:19:00.002-07:002018-09-03T05:04:49.419-07:00WebForms Dependency Injection<div style="text-align: justify;">
Entrada dedicada a Micha-kun; último bastión de defensa de WebForms; quien, según la fecha de su última entrada en su <a href="https://gachonet.wordpress.com/">blog</a>, esta <a href="https://es.wikipedia.org/wiki/Missing_in_Action">MIA</a>. Sospecho que los EmeUveCeros lo han raptado y lo tienen en una especie de Guantanamo tecnológico en Silicon Valley lavándole el cerebro.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
<a href="https://blogs.msdn.microsoft.com/dotnet/2018/04/30/announcing-the-net-framework-4-7-2/">https://blogs.msdn.microsoft.com/dotnet/2018/04/30/announcing-the-net-framework-4-7-2/</a></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Ahí lo tenemos! Los sueños húmedos de la especie en extinción de desarrolladores de WebForms hechos realidad.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Micha; si te escapas, te conectas a Internet y lees esto; deja un mensaje en los comentarios con la <a href="https://es.wikipedia.org/wiki/Esteganograf%C3%ADa">esteganografia</a> necesaria como para encontrarte y traerte de nuevo al excitante mundo de los WebForms.</div>
Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-6168465570663215053.post-85125215007465385442018-08-01T03:50:00.003-07:002021-06-07T23:11:36.644-07:00Hidratar agregado con event sourcing.<div style="text-align: justify;">
Esta entrada, al igual que las anteriores (las que van entre <a href="http://altplusf13.blogspot.com/2018/07/meditando-un-test-de-codigo.html">ésta </a>y <a href="http://altplusf13.blogspot.com/2018/07/los-eventos-nunca-fallan.html">ésta</a>), es generada por la conversación que tuve con unos desarrolladores de software hace no mucho.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Cuando comentábamos los pormenores de la implemntación de un pequeño <a href="http://altplusf13.blogspot.com/2018/07/meditando-un-test-de-codigo.html">test de código</a>, uno de ellos hizo un comentario con terribles implicaciones; puesto que denota una falta de profundidad en el entendimiento y comprensión sobre agregados, eventos y event sourcing (ES).</div>
<div style="text-align: justify;">
<br />
<a name='more'></a></div>
<div style="text-align: justify;">
El comentario fue: "Esto como agregado... no pinta bien." o algo así. Este comentario implica tantísimas cosas erróneas en los cimientos necesarios para aplicar correctamente agregados, eventos y event sourcing que no sé ni por donde empezar... pero lo voy a intentar.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Lo primero es que sin que se solicite explícitamente nadie va a diseñar un agregado hidratado por ES cuando programa el típico test de código de HackerRank o Codility; es un algoritmo y punto, no tiene más chicha que eso. Y lo segundo es que <b>NO</b> es posible. Si le pegáis un vistazo a la<a href="http://altplusf13.blogspot.com/2018/07/meditando-un-test-de-codigo.html"> entrada de este blog </a>sobre el test veréis que el input no son eventos por lo que es demencial pensar que con eso se puede hacer algo de agregados y ES.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Si el agregado de <i>Factura </i>tiene que aplicar un complejo algoritmo que necesita una estructura de datos <i>Diccionario </i>y tiene que recalcular la información a partir de datos sin estado estamos diseñando tremendamente mal los agregados y el ES.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
ES no significa recalcular el estado de todo ciegamente a partir de los datos sin ningún precalculo ni aplicación de ninguna regla de dominio; significa estado incremental a partir de estados más pequeños y contextualizados (los eventos). Los eventos<b> tienen estado</b>; un estado que, si se aplica de forma incremental en un agregado, refleja una consistencia en el dominio bien definida.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Leeros la entrada del <a href="http://altplusf13.blogspot.com/2018/07/meditando-un-test-de-codigo.html">test de código</a> venga. Os espero.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
¿Ya? Pues vamos a hablar de ello.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Pongamos sobre la mesa el agregado <i>Factura </i>y una operación de <i>GenerarFactura</i>. Para hidratar este agregado con ES debemos alimentarlo con eventos de trabajos realizados; parecido al test de código <b>pero no igual</b>. Los eventos de trabajos realizados deben tener un estado; que es el precio ya calculado de ese trabajo. Ese evento lo generaría un agregado <i>Trabajo </i>con su operación <i>RegistrarTrabajoRealizado</i>; es al registrar el trabajo realizado cuando el agregado calcula el precio que le corresponde a ese trabajo y lanza el evento <i>TrabajoRealizado </i>con su precio.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Ya nos hemos quitado de en medio el cálculo de precio por trabajo en el agregado <i>Factura</i>.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Con respecto a la necesidad de tener <i>el trabajo acumulado más largo</i> identificado para restarlo y aplicar la oferta a la factura, la cosa esta clara: Para el agregado <i>Factura</i>, <i>el trabajo más largo</i> es un Value Object (VO) que se usa para construir el agregado. Y el agregado simplemente lo utiliza para realizar la resta al precio final.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Resumiendo; el agregado <i>Factura </i>se hidrata con el VO <i>trabajo acumulado más largo</i> y los eventos de trabajos registrados con su precio. Por lo que el agregado simplemente tiene que ir aplicando los eventos en su estado interno (empieza con precio 0 y va sumando los precios de los trabajos incluidos en los eventos) y luego aplicar la oferta de restar el <i>trabajo acumulado más largo.</i> Una vez finalizado, este agregado lanza el evento <i>FacturaGenerada </i>con el precio final de la factura.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
¿Y como se obtiene el VO <i>trabajo acumulado más largo</i> en ES? </div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Pues depende mucho de las necesidades del dominio, rendimiento, etc.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
En el caso más simple podemos tener directamente ese dato precalculado en persistencia sin miedo; aunque algún talibán del ES nos diga que eso no es ES. Ni caso oye. Es un VO sin identidad que no recibe cambios por referencia, si no directos.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
También podemos tener un SQL o MapReduce que utilice los eventos de persistencia de <i>TrabajoRegistrado </i>para calcularlo. RavenDB es una maravilla en este caso y te permite tener predefinidos estos MapReduce en el repositorio; los cuales se lanzan automáticamente cuando la fuente de datos cambie y no tienes que "ensuciar" el esquema de datos de ES con VO's precalculados.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Otra opción es que el encargado de construir e hidratar el agregado <i>Factura </i>se encargue de calcular este VO para pasárselo al agregado. Ahí no tendría problemas para usar un Diccionario e ir acumulando los trabajos y obtener el resultado.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Existen más opciones y más complejas, mayormente por necesidades del dominio, que no voy a exponer aquí, pero seguro que ya os habéis hecho una idea sobre que, si no surgen necesidades más enrevesadas, esto es un VO y no hay que complicarse la vida más de lo necesario.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
¿Entendéis ahora por qué un comentario tan corto y tan sencillo implica tantos errores de concepción y diseño? Indica que estás un poco verde en temas absolutamente indispensables para una arquitectura decente DDD con ES; que no sabes bien como diseñar conceptualmente los agregados, los VO's y los eventos; que no sabes bien como aplicar ES y utilizarlo para rehidratar los agregados. Implica que no tienes bien asentados los cimientos necesarios y la arquitectura que estás construyendo tiene los pies de barro.</div>
Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-6168465570663215053.post-48437654139448055572018-07-11T02:21:00.001-07:002018-11-28T03:46:15.959-08:00Los eventos nunca fallan IIIÉpica la transparencia de la charla de ClearMeasure de Jeffrey Palermo con respecto a los "fallos" de los eventos:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKj6_NrG5gpmY9Enm0AAPQKDAOWlKPkZC83XVe33nufMTchdz7f4YEE0Y2wtlfSOgXGvNYZ4YeKMGponxqkFd0baEJic5cmtydF8Xr0f8pyl91ahnNB60OOjDTgcpCI-YurrbXAbISxXA/s1600/DontDoIt.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="398" data-original-width="1341" height="118" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjKj6_NrG5gpmY9Enm0AAPQKDAOWlKPkZC83XVe33nufMTchdz7f4YEE0Y2wtlfSOgXGvNYZ4YeKMGponxqkFd0baEJic5cmtydF8Xr0f8pyl91ahnNB60OOjDTgcpCI-YurrbXAbISxXA/s400/DontDoIt.png" width="400" /></a></div>
<span id="goog_1511614192"></span><span id="goog_1511614193"></span><br />Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-6168465570663215053.post-44340067256014683002018-07-06T03:52:00.000-07:002019-10-04T00:35:40.621-07:00Los eventos nunca fallan II<div style="text-align: justify;">
Me han puesto un ejemplo de un "fallo" de evento y creo que ya entiendo algunos de los casos por los que alguien puede llegar a creer que un evento falla. Vamos a ello.</div>
<div style="text-align: justify;">
<a name='more'></a><br /></div>
<div style="text-align: justify;">
Me proponen el siguiente escenario sobresimplificado y sin Sagas ni Event Sourcing, para no complicar la cosa (no me seáis quisquillosos tanto en cuanto la verdadera segregación de contextos y cosas así, asumamos que tiene que ser de esta manera para el caso que nos ocupa):</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Tenemos 2 contextos: Ventas y Contabilidad. El contexto de ventas se encarga de recibir el comando de que el usuario ha introducido su tarjeta de crédito y ha pulsado el botón de pagar un servicio.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Ventas utiliza un agregado para realiza las comprobaciones necesarias, todo está OK con respecto a su contexto (precio, servicio contratado, disponibilidad, etc). Ventas actualiza los estados de persistencia necesarios para marcar el servicio como pagado; y luego lanza un evento a Contabilidad "UserPaidTheService" pero resulta que ocurre algún problema al cargar el dinero y Contabilidad no puede embolsárselo. ¡Hacienda le tiene bloqueada la cuenta corriente!</div>
<div style="text-align: justify;">
</div>
<div style="text-align: justify;">
<i>- ¡Pues ya está, el evento falló, hay que notificarlo a Ventas para que deshaga su estado! ¿Ves, ves? ¡Te lo dije!</i></div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Que va, que va. El problema aquí es que no hemos diseñado bien el sistema de eventos. Pulsar un botón que pone "Pagar" no es pagar, es ordenar el pago. Algo no está pagado hasta que no tenemos el dinero en nuestro bolsillo. Ventas debe lanzar un evento que sea "UserOrderedPaidThisService" una vez comprobamos que la orden de pago es correcta con respecto al contexto de Ventas. Ese evento es manejado por Contabilidad que intentara hacer el cargo, y si todo está bien lanzará el evento "UserPaidTheService". Este nuevo evento será consumido por el departamento de Ventas y actualizará el estado al estado de pagado definitivo. Si el evento es "PaymentFailed" se puede simplemente notificar al usuario si no es necesario actualizar ningún estado en persistencia.</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Un detalle curioso que ha surgido colateralmente de la resolución de este ejemplo es darse cuenta de que aunque en el contexto de Ventas se han utilizado agregados para comprobar las reglas e invariantes del dominio para el pago; <b>si no es necesario o deseable,</b> no se tiene porque persistir ningún estado. El agregado de ventas solo nos ha retornado un OK a las reglas en forma de evento "UserOrderedPaidThisService".</div>
<div style="text-align: justify;">
<br /></div>
<div style="text-align: justify;">
Un agregado no tiene porque ser un elemento persistible, no tiene tampoco porque provocar el cambio de ningún estado. Todo eso son detalles de una cierta implementación u otra. Tanto en cuanto un agregado se dedique a comprobar la reglas de dominio es completamente aceptable como agregado.</div>
Unknownnoreply@blogger.com9