[{"content":"¡Domina el Código Legacy! ¿Te enfrentas a código legacy que da miedo tocar? En esta guía vamos a ver paso a paso cómo convertir ese código problemático en algo más mantenible. Basándome en técnicas recogidas del legendario libro \u0026ldquo;Working Effectively with Legacy Code\u0026rdquo; de Michael Feathers, te mostraré un enfoque para abordar este desafío. Además, comentaré otra serie de herramientas que he ido aprendiendo a medida que me iban siendo útiles en los procesos de refactoring.\nEl refactoring es una práctica fundamental para mantener el código sostenible. ¿Por qué es crucial dominar el refactoring? Simple:\nTu código será más fácil de mantener y modificar Desarrollarás nuevas features más rápido Disminuyes la probabilidad de bugs futuros Sin embargo, refactorizar puede ser un desafío significativo, sobre todo cuando estamos modificando un código que no tiene test. ¿Cómo podemos garantizar que dicha modificación no ha cambiado comportamiento rompiendo así el funcionamiento actual? Sin tests, estás volando a ciegas. No importa si tu código tiene mil dependencias o llamadas a API externas - siempre hay una manera de testearlo. Los tests no solo te protegen de errores, sino que te ahorran horas de debugging.\nPaso 1: Mapea el Terreno Antes de tocar una sola línea de código, necesitas entender el sistema. Tu primera tarea será ser un detective. Necesitas investigar el código. Si ya existe documentación sobre el código, te será más fácil ir relacionando esa información con el código y te será más fácil entenderlo. Si se da el caso de que no hay documentación (algo más común de lo que me gustaría), el proceso será algo más difícil y enrevesado, pero no imposible. Tómate todo el tiempo que necesites para leer el código e ir documentando el flujo o caso de uso completo por el que transcurre una funcionalidad de la aplicación. Lo ideal es que registres también esas zonas del código que crees que pueden ser mejoradas.\nOtra herramienta que te puede venir muy bien para esto es crear diagramas. Aquí te puedes apoyar en los estándares de la industria como diagramas de secuencia y demás, pero incluso unos cuantos cuadrados y flechas pueden ayudarte enormemente a entender el sistema. Una herramienta de pintura estilo Paint o Excalidraw te serán de gran ayuda para empezar a realizar esos primeros bocetos que te ayuden a construir el esquema mental de como funciona el código.\nVamos a poner un ejemplo muy básico de cómo puede ser un dibujo que nos empiece a dar pistas sobre cómo funciona actualmente el código:\nEn el dibujo hemos agrupado los distintos colaboradores que interactúan en un proceso de guardado de usuario. Gracias a tenerlo esquematizado de esta forma podremos detectar qué partes no tienen sentido o cómo se pueden mejorar. En el diagrama hemos incluido marcas de tiempo donde más se demora el código para tener una pista de qué lugares pueden ser mejorados en lo que a eficiencia se refiere. Para las marcas de tiempo hemos usado el profiler de IntelliJ, una herramienta que se ejecuta cuando lanzas el proceso y que irá monitoreando cuánto tarda en ejecutarse cada función de tu código. Será tu mejor amigo para encontrar cuellos de botella.\nSeguro que este caso en concreto no será lo más difícil que te encuentres por ahí, pero espero que pueda aportar algo de inspiración para poder adaptar esta técnica a tus necesidades.\nAhora que tenemos los puntos de mejora localizados y tenemos una foto donde se muestra qué es lo que hace todo el proceso. Gracias a esto, ahora vamos a poder priorizar qué partes tiene más sentido o mayor retorno de valor ir afrontando y mejorando primero.\nPaso 2: Libérate de las cadenas Ahora que ya tienes claro qué pieza del código quieres modificar lo siguiente sería añadir test que lo cubran si es que aún no los tiene. Sin embargo, cuando hablamos de código legado es muy común que realizar un test a una clase sea bastante complicado, ya que tiene ciertas dependencias que no son posibles de instanciar o crear en un entorno de test. Veamos qué técnicas podemos aplicar para romper dependencias:\nInversión de Dependencias (El principio D de SOLID) Si tu clase depende de implementaciones concretas (emails, bases de datos\u0026hellip;), usa interfaces. Es más limpio que heredar y más flexible. Por ejemplo:\nAntes de extraer la interfaz\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class ServicioAlerta { private NotificadorEmail notificadorEmail; public ServicioAlerta(NotificadorEmail notificadorEmail) { this.notificadorEmail = notificadorEmail; } public void enviarAlerta(String mensaje) { notificadorEmail.enviarMensaje(mensaje); } } public class NotificadorEmail { public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando email: \u0026#34; + mensaje); } } public class Main { public static void main(String[] args) { ServicioAlerta servicio = new ServicioAlerta(new NotificadorEmail()); servicio.enviarAlerta(\u0026#34;Alerta de prueba por email!\u0026#34;); } } Después de extraer la interfaz\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public interface Notificador { void enviarMensaje(String mensaje); } public class NotificadorEmail implements Notificador { @Override public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando email: \u0026#34; + mensaje); } } public class NotificadorSMS implements Notificador { @Override public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando SMS: \u0026#34; + mensaje); } } public class ServicioAlerta { private final Notificador notificador; public ServicioAlerta(Notificador notificador) { this.notificador = notificador; } public void enviarAlerta(String mensaje) { notificador.enviarMensaje(mensaje); } } public class Main { public static void main(String[] args) { ServicioAlerta servicioEmail = new ServicioAlerta(new NotificadorEmail()); servicioEmail.enviarAlerta(\u0026#34;Alerta de prueba por email!\u0026#34;); ServicioAlerta servicioSMS = new ServicioAlerta(new NotificadorSMS()); servicioSMS.enviarAlerta(\u0026#34;Alerta de prueba por SMS!\u0026#34;); } } De esta forma hemos reducido el acoplamiento. Ahora ServicioAlerta depende de la abstracción (Notificador), no de sus implementaciones concretas, algo que además de darnos flexibilidad, ya que permite añadir y más implementaciones sin modificar el módulo de alto nivel, también nos da la ventaja de que en los test vamos a poder crear una implementación “falsa” que evite usar implementaciones de SMS o email que llegarían a usuarios finales.\nShadowing de Clases El shadowing es una técnica que consiste en redefinir el comportamiento de una clase en tiempo de ejecución (o en tiempo de construcción del objeto) para sustituir su funcionalidad sin modificar directamente la clase que la usa. Aunque es menos común que el uso de inyección de dependencias, puede ser útil en ciertos escenarios para evitar modificaciones mayores en el código existente.\nExisten dos formas de hacerlo y vamos a ver cada una como si en el ejemplo anterior en lugar de haber usado inversión de dependencias usáramos shadowing:\nExtendiendo la clase original y redefiniendo su comportamiento En este ejemplo vemos que nos aprovechamos de la herencia para extender la clase original y sobreescribir el funcionamiento de su método. Después, cuando creamos la clase de alto nivel, le pasamos por constructor la clase que hace shadowing, algo que nos va a permitir establecer un comportamiento totalmente distinto en los test.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class NotificadorEmail { public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando email: \u0026#34; + mensaje); } } public class NotificadorEmailSMS extends NotificadorEmail { @Override public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando SMS: \u0026#34; + mensaje); } } public class ServicioAlerta { private final NotificadorEmail notificador; public ServicioAlerta(NotificadorEmail notificador) { this.notificador = notificador; } public void enviarAlerta(String mensaje) { notificador.enviarMensaje(mensaje); } } public class Main { public static void main(String[] args) { ServicioAlerta servicioEmail = new ServicioAlerta(new NotificadorEmail()); servicioEmail.enviarAlerta(\u0026#34;Alerta de prueba por email!\u0026#34;); ServicioAlerta servicioSMS = new ServicioAlerta(new NotificadorEmailSMS()); servicioSMS.enviarAlerta(\u0026#34;Alerta de prueba por SMS!\u0026#34;); } } Shadowing en la carpeta de test En este caso no hemos hecho uso de la herencia, sino que hemos creado una clase con el mismo nombre y en la misma localización que la clase original, pero en este caso, dentro de la carpeta de test. Esto funciona porque en el entorno de pruebas, el compilador y el classloader de Java priorizan la clase en src/test/java sobre la de src/main/java cuando comparten el mismo nombre y paquete. Esto permite sustituir la implementación sin modificar el código de producción. Por este mismo hecho, hay que entender que es una solución de la cual no deberíamos abusar. No es una solución intuitiva para quien intente interpretar nuestro código. Por lo que úsala si te resulta necesario, con moderación, y entendiendo que es una solución transitoria.\nEn src/main/java\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.ejemplo.notificaciones; public class ServicioAlerta { private final NotificadorEmail notificador; public ServicioAlerta(NotificadorEmail notificador) { this.notificador = notificador; } public void enviarAlerta(String mensaje) { notificador.enviarMensaje(mensaje); } } package com.ejemplo.notificaciones; public class NotificadorEmail { public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando email: \u0026#34; + mensaje); } } En src/test/java\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.ejemplo.notificaciones; public class NotificadorEmail { private String ultimoMensaje; public void enviarMensaje(String mensaje) { this.ultimoMensaje = mensaje; System.out.println(\u0026#34;Mock: mensaje capturado - \u0026#34; + mensaje); } public String getUltimoMensaje() { return ultimoMensaje; } } package com.ejemplo.notificaciones; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; public class ServicioAlertaTest { @Test public void testEnviarAlertaConShadowing() { ServicioAlerta servicio = new ServicioAlerta(new NotificadorEmail()); servicio.enviarAlerta(\u0026#34;Mensaje de prueba\u0026#34;); NotificadorEmail mock = (NotificadorEmail) servicio.getClass() .getDeclaredField(\u0026#34;notificador\u0026#34;) .get(servicio); assertEquals(\u0026#34;Mensaje de prueba\u0026#34;, mock.getUltimoMensaje()); } } Elimina Dependencias Ocultas En ocasiones nos podemos encontrar dependencias ocultas. Son dependencias de las cuales no se tiene una dependencia de forma explícita que sea perceptible en la construcción de nuestra clase.\nDependencia directa Algunas son colaboradores de los que depende la clase pero en lugar de recibir su instancia por constructor se instancia directamente en la clase. Esto hace muy difícil hacer un test de dicha clase, ya que no nos permite modificar el comportamiento de la clase para los test y, por lo tanto, si volvemos al ejemplo de antes, estaríamos enviando emails o SMS a usuarios finales. También puedes mantener el constructor antiguo si es necesario para no romper el código existente. Por ejemplo:\nAntes de mover la dependencia al constructor\n1 2 3 4 5 6 7 8 9 10 11 public class ServicioAlerta { private final NotificadorEmail notificador; public ServicioAlerta() { this.notificador = new NotificadorEmail(); } public void enviarAlerta(String mensaje) { notificador.enviarMensaje(mensaje); } } Después de mover la dependencia al constructor\n1 2 3 4 5 6 7 8 9 10 11 public class ServicioAlerta { private final NotificadorEmail notificador; public ServicioAlerta(NotificadorEmail notificadorEmail) { this.notificador = notificadorEmail; } public void enviarAlerta(String mensaje) { notificador.enviarMensaje(mensaje); } } Singleton Los singletons son otro tipo de dependencias ocultas. Veamos cuáles son las técnicas más rápidas que podemos aplicar para poder testear una clase que se apoya en un singleton.\nAñade un setter para testing\nAntes\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class NotificadorEmail { private static final NotificadorEmail instancia = new NotificadorEmail(); private NotificadorEmail() {} public static NotificadorEmail getInstance() { return instancia; } public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando email: \u0026#34; + mensaje); } } public class ServicioAlerta { public void enviarAlerta(String mensaje) { NotificadorEmail.getInstance().enviarMensaje(mensaje); } } Después\npublic class NotificadorEmail { private static NotificadorEmail instancia = new NotificadorEmail(); private NotificadorEmail() {} public static NotificadorEmail getInstance() { return instancia; } public static void setInstance(NotificadorEmail nuevaInstancia) { instancia = nuevaInstancia; } public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando email: \u0026#34; + mensaje); } } public class ServicioAlerta { public void enviarAlerta(String mensaje) { NotificadorEmail.getInstance().enviarMensaje(mensaje); } } public class NotificadorEmailMock extends NotificadorEmail { private String ultimoMensaje; @Override public void enviarMensaje(String mensaje) { this.ultimoMensaje = mensaje; } public String getUltimoMensaje() { return ultimoMensaje; } } public class ServicioAlertaTest { @Test public void testEnviarAlerta() { NotificadorEmailMock mock = new NotificadorEmailMock(); NotificadorEmail.setInstance(mock); ServicioAlerta servicio = new ServicioAlerta(); servicio.enviarAlerta(\u0026#34;Mensaje de prueba\u0026#34;); assertEquals(\u0026#34;Mensaje de prueba\u0026#34;, mock.getUltimoMensaje()); } } Crea una subclase con constructor protected\nAntes\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class NotificadorEmail { private static final NotificadorEmail instancia = new NotificadorEmail(); private NotificadorEmail() {} public static NotificadorEmail getInstance() { return instancia; } public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando email: \u0026#34; + mensaje); } } public class ServicioAlerta { public void enviarAlerta(String mensaje) { NotificadorEmail.getInstance().enviarMensaje(mensaje); } } Después\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class NotificadorEmail { private static NotificadorEmail instancia = new NotificadorEmail(); protected NotificadorEmail() {} public static NotificadorEmail getInstance() { return instancia; } public static void usarMockParaPruebas(NotificadorEmail mock) { instancia = mock; } public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando email: \u0026#34; + mensaje); } } public class NotificadorEmailMock extends NotificadorEmail { private String ultimoMensaje; protected NotificadorEmailMock() {} @Override public void enviarMensaje(String mensaje) { this.ultimoMensaje = mensaje; } public String getUltimoMensaje() { return ultimoMensaje; } } public class ServicioAlerta { public void enviarAlerta(String mensaje) { NotificadorEmail.getInstance().enviarMensaje(mensaje); } } public class ServicioAlertaTest { @Test public void testEnviarAlerta() { NotificadorEmailMock mock = new NotificadorEmailMock(); NotificadorEmail.usarMockParaPruebas(mock); ServicioAlerta servicio = new ServicioAlerta(); servicio.enviarAlerta(\u0026#34;Mensaje de prueba\u0026#34;); assertEquals(\u0026#34;Mensaje de prueba\u0026#34;, mock.getUltimoMensaje()); } } Extrae una interfaz\nAntes\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class NotificadorEmail { private static final NotificadorEmail instancia = new NotificadorEmail(); private NotificadorEmail() {} public static NotificadorEmail getInstance() { return instancia; } public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando email: \u0026#34; + mensaje); } } public class ServicioAlerta { public void enviarAlerta(String mensaje) { NotificadorEmail.getInstance().enviarMensaje(mensaje); } } Después\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public interface Notificador { void enviarMensaje(String mensaje); } public class NotificadorEmail implements Notificador { private static final Notificador instancia = new NotificadorEmail(); private NotificadorEmail() {} public static Notificador getInstance() { return instancia; } @Override public void enviarMensaje(String mensaje) { System.out.println(\u0026#34;Enviando email: \u0026#34; + mensaje); } } public class ServicioAlerta { private final Notificador notificador; public ServicioAlerta(Notificador notificador) { this.notificador = notificador; } public void enviarAlerta(String mensaje) { notificador.enviarMensaje(mensaje); } } public class NotificadorMock implements Notificador { private String ultimoMensaje; @Override public void enviarMensaje(String mensaje) { this.ultimoMensaje = mensaje; } public String getUltimoMensaje() { return ultimoMensaje; } } public class ServicioAlertaTest { @Test public void testEnviarAlerta() { NotificadorMock mock = new NotificadorMock(); ServicioAlerta servicio = new ServicioAlerta(mock); servicio.enviarAlerta(\u0026#34;Mensaje de prueba\u0026#34;); assertEquals(\u0026#34;Mensaje de prueba\u0026#34;, mock.getUltimoMensaje()); } } Paso 4: Construye tu Red de Seguridad Como hemos mencionado anteriormente, los tests son lo que necesitas para poder garantizar que las modificaciones que haces en el código no alteran comportamiento o estén rompiendo la aplicación. Necesitas tests unitarios, ya que son los más rápidos y precisos. Son tu primera línea de defensa. Nada de bases de datos o HTTP aquí. Sobre esto, Michael Feathers nos comenta que el grado de cuan seguro es realizar un cambio sobre una pieza es proporcional a la cercanía que tienen los test que cubren dicha parte. Con los test unitarios podemos cumplir esta premisa. También nos vendrán muy bien los tests de integración, para asegurar que funcionan correctamente nuestras piezas de infraestructura como bases de datos, API externas, etc. Finalmente, también debemos tener algunos test end to end que verifique que la funcionalidad en todo su conjunto, lógica de negocio, infraestructura y demás funcione perfectamente toda junta.\nEs posible que te encuentres atascado en la creación de test porque aún no tienes el conocimiento suficiente de la lógica de negocio o el dominio del código como para poder empezar a escribir los primeros tests. Para solucionar esto, tenemos Approval Testing. Esta herramienta nos propone una alternativa al clásico unit test. En este caso no definimos una aserción concreta, ya que no conocemos la lógica de negocio involucrada. La herramienta se limita a sacar una instantánea del resultado actual código. Siempre que generemos la misma instantánea diremos que estamos manteniendo la misma lógica. Veo esto realmente útil para poder avanzar en las mejoras de un código legacy con seguridad y rapidez. Bajo mi propia experiencia, he visto que también es de bastante útil para realizar aserciones complejas, como por ejemplo, el típico test que comprueba que tu implementación de JDBC graba correctamente en la base de datos y así no tener que comprobar columna a columna el valor, sino que le saca la instantánea a toda la tabla. Veamos un ejemplo de cómo funciona approval testing:\nCódigo que usa la librería Approvals\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 import org.approvaltests.Approvals; import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; class ApprovalTest { @Test void testCustomerList() { List\u0026lt;Customer\u0026gt; customers = getCustomers(); Approvals.verify(customers); } public List\u0026lt;Customer\u0026gt; getCustomers() { Supplier\u0026lt;List\u0026lt;Customer\u0026gt;\u0026gt; customerSupplier = () -\u0026gt; { List\u0026lt;Customer\u0026gt; customers = new ArrayList\u0026lt;\u0026gt;(); customers.add(createCustomer(\u0026#34;John Doe\u0026#34;, 35)); customers.add(createCustomer(\u0026#34;Jane Smith\u0026#34;, 28)); customers.add(createCustomer(\u0026#34;Bob Johnson\u0026#34;, 42)); return customers; }; List\u0026lt;Customer\u0026gt; customerList = customerSupplier.get(); List\u0026lt;Customer\u0026gt; shuffledList = shuffleCustomers(customerList); List\u0026lt;Customer\u0026gt; sortedList = sortCustomersByAge(shuffledList); return sortedList.stream() .filter(customer -\u0026gt; customer.getAge() \u0026gt; 0) .collect(Collectors.toList()); } private Customer createCustomer(String name, int age) { return CustomerFactory.create(name, age); } private List\u0026lt;Customer\u0026gt; shuffleCustomers(List\u0026lt;Customer\u0026gt; customers) { List\u0026lt;Customer\u0026gt; shuffled = new ArrayList\u0026lt;\u0026gt;(customers); Collections.shuffle(shuffled); return shuffled; } private List\u0026lt;Customer\u0026gt; sortCustomersByAge(List\u0026lt;Customer\u0026gt; customers) { return customers.stream() .sorted(Comparator.comparingInt(Customer::getAge)) .collect(Collectors.toList()); } private static class CustomerFactory { public static Customer create(String name, int age) { return new CustomerBuilder().setName(name).setAge(age).build(); } } private static class CustomerBuilder { private String name; private int age; public CustomerBuilder setName(String name) { this.name = name; return this; } public CustomerBuilder setAge(int age) { this.age = age; return this; } public Customer build() { return new Customer(name, age); } } public static class Customer { private final String name; private final int age; public Customer(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return \u0026#34;Customer{name=\u0026#39;\u0026#34; + name + \u0026#34;\u0026#39;, age=\u0026#34; + age + \u0026#34;}\u0026#34;; } } } El archivo que genera la ejecución de dicha librería:\n----------------- usando verify -------------------------------- Customer{name=\u0026#39;John Doe\u0026#39;, age=35} Customer{name=\u0026#39;Jane Smith\u0026#39;, age=28} Customer{name=\u0026#39;Bob Johnson\u0026#39;, age=42} ----------------- o también usando verifyJson -------------------------------- [ { \u0026#34;name\u0026#34;: \u0026#34;John Doe\u0026#34;, \u0026#34;age\u0026#34;: 35 }, { \u0026#34;name\u0026#34;: \u0026#34;Jane Smith\u0026#34;, \u0026#34;age\u0026#34;: 28 }, { \u0026#34;name\u0026#34;: \u0026#34;Bob Johnson\u0026#34;, \u0026#34;age\u0026#34;: 42 } ] Otra herramienta que te será de utilidad es el medidor de cobertura de tu IDE. Puedes poner que se ejecute junto con los test y lo que hará es ver que partes del código están cubiertas por test. No te tomes esto como el indicador mágico, ya que un 100% no es sinónimo de no haya bugs, y de hecho tanto porcentaje es contraproducente. Hay cosas que sencillamente no tiene sentido testear, como un getter u otros métodos con una lógica extremadamente sencilla. En realidad, un porcentaje que este sobre el 70% y el 80% con test de calidad que verdaderamente prueben las diferentes casuísticas ya es más que suficiente. Ten en cuenta también que aunque la cobertura indique que hay algún test que cubre cierto bloque de código, es posible que el test este escrito para cubrir otra parte y, por lo tanto, aunque la herramienta te diga que esa parte está completa, deberías de escribir un test dedicado para esa parte y sus diferentes casuísticas. Si tienes en cuenta sus limitaciones y sus bondades, verás que es una gran herramienta para detectar qué código necesita más tests.\nPaso 5: Hora de la Acción Con los test en su lugar, es hora de mejorar el código.\nCuando estamos haciendo refactoring, aconsejo ir siempre al cambio más sencillo. Si estamos ante método que te resulta complicado de entender comienza por renombrar alguna variable, o extraerla. Comienza a darle semántica al método. Verás que poco a poco iras ganando en conocimiento sobre su cometido, y cuando menos te des cuenta ya lo estarás dominando. Es en ese momento de control cuando podemos venirnos arriba y comenzar a introducir algunas abstracciones o lo que se necesite para que el código sea más sencillo.\nSi el objetivo no es un refactor como tal, sino añadir una nueva feature, y estamos antes un método monstruoso, quizá lo más rentable sea hacer la funcionalidad nueva en otro método que se llame desde el actual. Todo por no seguir añadiendo complejidad en un bloque donde ya hay demasiada. También se puede crear otra clase que haga dicha funcionalidad y que la clase actual la llame. La elección entre ambos dependerá de cuan cargada este la clase actual, o si lo nuevo que se quiere introducir no es de su responsabilidad. Ambas soluciones son muy buenas también cuando tenemos un método muy difícil de testear o una clase muy difícil de instanciar y preparar en los tests. Nos ayudará a ir sacando trabajo adelante.\nPrioriza siempre en la medida de lo posible estar la menor cantidad de tiempo con los test en rojo para que no perdamos el control. Acostúmbrate a lanzar habitualmente los tests con cada cambio, y verificar que no se han roto. Organiza los cambios de la forma menos disruptiva posible. Si vas a introducir una abstracción nueva, plantéala de tal forma que sea compatible con el código actual para que los test sigan en verde, para luego seguir iterándola y mejorándola hasta que quede al gusto. Tienes que verlo como irle dando forma a la escultura (nuestro código), a base de pequeños golpes bien pensados.\nRefactorizar es el arte de hacer una sola cosa a la vez. Si ves algo más que mejorar mientras trabajas, anótalo para después. No abras varios frentes a la vez, especialmente con código legacy. Esto quiere decir que tenemos que estar concentrados en el objetivo. Si nuestro foco está puesto en mejorar una clase y mientras navegamos llegamos a otra y vemos algo que mejorar, te lo apuntas, ya sea en una libreta o bloc de notas del ordenador, o bien con un TODO en el código, pero jamás lo modificarás en ese momento.\nConclusión El código legacy puede ser intimidante, pero siguiendo estas técnicas y consejos espero que podrás dar un paso más en dominarlo. El libro \u0026ldquo;Working Effectively with Legacy Code\u0026rdquo; tiene aún más trucos y recomiendo encarecidamente su lectura para mejorar como profesional y porque creo que tiene muchas técnicas que yo no he mencionado en este post. Además, su lectura es muy amena y los capítulos están estructurados de tal forma que cada uno plantea resolver un problema concreto que se te puede dar afrontando un código legacy. El refactoring es un proceso continuo, no una tarea única, debe ser aplicado diariamente tanto en proyectos legacy para que sean mejorados, como en proyectos greenfield para que mantengan esa frescura.\nTeniendo un IDE que hace refactors automáticos no vale la excusa de algo no se puede refactorizar porque no se le puede añadir test. Siempre se puede mover código molesto o lo que sea a un método, heredarlo en una clase de test y cambiar su comportamiento.\n¡Ahora ve y mejora ese código!\n","permalink":"https://raulpadilladelgado.github.io/blog/p/gu%C3%ADa-para-modificar-c%C3%B3digo-legacy/","summary":"\u003ch2 id=\"domina-el-código-legacy\"\u003e¡Domina el Código Legacy!\u003c/h2\u003e\n\u003cp\u003e¿Te enfrentas a código legacy que da miedo tocar? En esta guía vamos a ver paso a paso cómo convertir ese código problemático en algo más mantenible. Basándome en técnicas recogidas del legendario libro \u0026ldquo;Working Effectively with Legacy Code\u0026rdquo; de Michael Feathers, te mostraré un enfoque para abordar este desafío. Además, comentaré otra serie de herramientas que he ido aprendiendo a medida que me iban siendo útiles en los procesos de refactoring.\u003c/p\u003e","title":"Guía para modificar código legacy"},{"content":"Introducción Cuando estamos desarrollando algún proyecto es muy común que necesitemos una dependencia externa al propio proyecto que estamos realizando como pueda ser una base de datos, un sistema de mensajería, etc. Nuestra aplicación está pensada para que sea desplegada en un entorno que no sea la máquina donde estamos programando para que usuarios finales puedan usarla. Podría ser “staging” y que sea entonces para usuarios que se dedicaran a probarla. También podría ser “production” y que sea entonces para usuarios finales, que probablemente paguen por el producto que desarrollamos. Sin embargo, no debemos olvidar que nosotros, como desarrolladores, también somos usuarios de alguna forma cuando se trata del entorno “local”.\nEn este post mi idea es exponer como a partir de la versión 3.1 de Spring Boot podemos simplificar la forma en la que ejecutamos nuestro proyecto en local.\nCaso típico En mi paso por distintos proyectos he visto que lo más común cuando se trata de ejecutar la aplicación en el entorno local sea tener un archivo docker-compose.yml el cual tiene la definición de como se debe levantar nuestra aplicación y el resto de servicios colaboradores, como una base de datos, en contenedores sobre nuestro sistema. Veamos un ejemplo:\ndocker-compose.yml\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 version: \u0026#39;3.8\u0026#39; services: db: image: postgres:latest environment: POSTGRES_DB: songs POSTGRES_USER: user POSTGRES_PASSWORD: password ports: - \u0026#34;5432:5432\u0026#34; app: build: context: . dockerfile: docker/Dockerfile ports: - \u0026#34;8080:8080\u0026#34; depends_on: - db environment: SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/songs En el archivo se define una base de datos Postgres además de nuestra aplicación Spring Boot. En el Dockerfile que crea la imagen en la que se basa la definición del contenedor de la aplicación se copia el Jar generado en el contenedor y se ejecuta:\nDockerfile\n1 2 3 4 FROM openjdk:21-jdk-slim ARG JAR_FILE=build/libs/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT [\u0026#34;java\u0026#34;,\u0026#34;-jar\u0026#34;,\u0026#34;/app.jar\u0026#34;] Este planteamiento es perfectamente válido, pero podemos optimizarlo porque lo más probable es que ya estemos usando Testcontainers para levantar los servicios externos que necesitan nuestros test de integración, y nos vamos a valer de esa configuración para mejorar nuestra experiencia. Veamos primero como podría ser una configuración del Testcontainers:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @TestConfiguration class PostgresTestContainerConfiguration { companion object { private val container: PostgreSQLContainer\u0026lt;*\u0026gt; = PostgreSQLContainer(\u0026#34;postgres:latest\u0026#34;).apply() { start() } @DynamicPropertySource @JvmStatic private fun configureDatasource(registry: DynamicPropertyRegistry) { registry.add(\u0026#34;spring.datasource.url\u0026#34;, container::getJdbcUrl) registry.add(\u0026#34;spring.datasource.username\u0026#34;, container::getUsername) registry.add(\u0026#34;spring.datasource.password\u0026#34;, container::getPassword) } } } Tenemos una clase que se encarga de crear un contenedor Postgres y sobreescribir en el contexto de Spring la configuración relacionada con la base de datos, como la URL.\nYa hemos visto el ejemplo más común que te puedes encontrar. Vamos a ver ahora como exprimir lo que nos ofrece Spring Boot a partir de su versión 3.1 para mejorar toda esta configuración.\nAutoconfiguración de la conexión En el último ejemplo del apartado anterior hemos visto que las propiedades de conexión de la base de datos se definían de manera manual sobreescribiendo el contexto de Spring. Veamos como podemos hacer lo mismo pero delegando ese trabajo al framework:\nPostgresTestContainerConfiguration.kt (Antes)\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @TestConfiguration class PostgresTestContainerConfiguration { companion object { private val container: PostgreSQLContainer\u0026lt;*\u0026gt; = PostgreSQLContainer(\u0026#34;postgres:latest\u0026#34;).apply() { start() } @DynamicPropertySource @JvmStatic private fun configureDatasource(registry: DynamicPropertyRegistry) { registry.add(\u0026#34;spring.datasource.url\u0026#34;, container::getJdbcUrl) registry.add(\u0026#34;spring.datasource.username\u0026#34;, container::getUsername) registry.add(\u0026#34;spring.datasource.password\u0026#34;, container::getPassword) } } } PostgresTestContainerConfiguration.kt (Después)\n1 2 3 4 5 6 @TestConfiguration(proxyBeanMethods = false) class PostgresTestContainerConfiguration { @Bean @ServiceConnection fun postgresContainer() = PostgreSQLContainer(\u0026#34;postgres:13\u0026#34;) } Usando la combinación @Bean junto con @ServiceConnection, Spring es capaz de autoinyectar las configuraciones de la base de datos en el contexto sin necesidad de que lo hagamos nosotros.\nEjecutando aplicación en local usando Testcontainers Nos podemos quitar la responsabilidad de mantener los archivos de Docker que mencionamos anteriormente y usar la misma configuración que tenemos para los test que usan Testcontainers. Solo tenemos que crear un método main en la carpeta de test, el cual usaremos para ejecutar nuestra aplicación desde el IDE de preferencia (en mi caso es IntelliJ):\nLocalApp.kt\n1 2 3 4 5 fun main(args: Array\u0026lt;String\u0026gt;) { fromApplication\u0026lt;TestcontainersLocalDevelopmentApplication\u0026gt;() .with(PostgresTestContainerConfiguration::class.java) .run(*args) } Lo que hacemos es usar el método fromApplication de Spring Boot para elegir qué clase es la que contiene el main del proyecto, apuntamos a la clase que está contenida en el código productivo, y además añadimos que tiene que cargar la configuración del contenedor que hemos visto anteriormente para que así levante los contenedores necesarios para los servicios externos de los que depende nuestra aplicación.\nCasos límite Contenedor genérico Testcontainers tiene bastantes módulos que simplifican la integración de contenedores para nuestros tests. En el código de antes vimos como levantamos un contenedor de PostgreSQL aprovechando la pre configuración que nos hace al ser un módulo de Testcontainers que podemos importar en nuestro proyecto.\nDicho esto, tampoco te será tan raro encontrarte en una situación en la que Testcontainers oficialmente no tenga una integración para una tecnología que quieres levantar en un contenedor para tus test. Sin embargo, aún es posible que saquemos provecho de usar Testcontainers para lanzar la aplicación en local.\nVamos a crear la configuración del contenedor:\nUnofficialModuleTestContainerConfiguration.kt\n1 2 3 4 5 6 7 8 @TestConfiguration(proxyBeanMethods = false) class UnofficialModuleTestContainerConfiguration { @Bean fun genericContainer(dynamicPropertyRegistry: DynamicPropertyRegistry): GenericContainer\u0026lt;*\u0026gt; { dynamicPropertyRegistry.add(\u0026#34;some.property\u0026#34;) { \u0026#34;some-value\u0026#34; } return GenericContainer(\u0026#34;image-without-testcontainers-support\u0026#34;) } } Como vemos, la diferencia con el otro contenedor que habíamos configurado es que este no tiene la anotación @ServiceConnection, la cual establecía automáticamente las propiedades de conexión del contenedor en el contexto de Spring. Como no existe módulo oficial en Testcontainers para la tecnología que queremos levantar el contendor, tenemos que usar DynamicPropertyRegistry para establecer nosotros manualmente esas propiedades en el contexto.\nConclusión Nos ha quedado una forma muy sencilla de usar nuestra aplicación en local sin mayor necesidad que darle al botón de play en el IDE. Para revisar el código de ejemplo que usé, por aquí tienes el repositorio que hice para guiar el blog:\nhttps://github.com/raulpadilladelgado/testcontainers-for-local-development\nEn la rama docker-compose-for-the-local-environment tienes la forma tradicional de levantar la aplicación apoyándonos en archivos Docker. En la otra rama testcontainers-for-the-local-environment tienes las mejoras de las que hemos hablado.\nEso es todo, espero que este post te haya servido. ¡Muchas gracias por leer!\n","permalink":"https://raulpadilladelgado.github.io/blog/p/testcontainers-para-ejecuci%C3%B3n-local/","summary":"\u003ch1 id=\"introducción\"\u003eIntroducción\u003c/h1\u003e\n\u003cp\u003eCuando estamos desarrollando algún proyecto es muy común que necesitemos una dependencia externa al propio proyecto que estamos realizando como pueda ser una base de datos, un sistema de mensajería, etc. Nuestra aplicación está pensada para que sea desplegada en un entorno que no sea la máquina donde estamos programando para que usuarios finales puedan usarla. Podría ser “staging” y que sea entonces para usuarios que se dedicaran a probarla. También podría ser “production” y que sea entonces para usuarios finales, que probablemente paguen por el producto que desarrollamos. Sin embargo, no debemos olvidar que nosotros, como desarrolladores, también somos usuarios de alguna forma cuando se trata del entorno “local”.\u003c/p\u003e","title":"Testcontainers para ejecución local"},{"content":"¿Qué es un linter? Los linter son herramientas esenciales en proyectos de código, ya que ayudan a mantener una base de código consistente y de alta calidad. Analizan el código en busca de posibles errores, violaciones de estilo y otros problemas, asegurando que el código siga las mejores prácticas y estándares de codificación acordados por el equipo. Al utilizar una herramienta de linter, podemos detectar y solucionar problemas temprano, lo que resulta en un código más limpio y un proceso de desarrollo más fluido. Con la capacidad de aplicar convenciones de codificación, identificar posibles errores y mejorar la legibilidad del código, una herramienta de linter es un activo valioso para cualquier proyecto de código. Al mantener un código consistente y de alta calidad, se facilita la colaboración entre los miembros del equipo, se reducen los conflictos y se mejora la eficiencia en el desarrollo.\nCuando hablamos de linter, nos encontramos con opciones muy populares y extendidas. Podríamos destacar ESLint en JavaScript, entre muchas otras que existan. En este post vengo a contar un caso muy particular, en el cual integro un linter muy completo en una aplicación flutter.\nLinter para flutter En el caso particular de que estés usando Flutter en tu proyecto, tenemos la opción de usar el analizador de código que viene por defecto con flutter. Con este, podemos configurar una serie de reglas en el archivo analysis_options.yaml que serán interpretadas cuando ejecutemos el comando flutter analyze. Nos las mencionaré todas porque hay una gran cantidad, pero puedes consultarlas en este enlace. Muchas tienen asociado un Quick fix, lo que viene a ser que pulsando un botón se aplica una sugerencia automática para el problema que nos detecta el linter. En sí la capacidad de ejecutar el linter viene por defecto con flutter, pero para importar una serie de reglas recomendadas por el equipo de flutter añadiremos como dependencia el paquete \u0026lsquo;flutter_lints\u0026rsquo; a nuestro proyecto y lo usaremos de la siguiente forma:\nanalysis_options.yaml\n1 2 3 4 5 6 7 8 9 10 11 12 13 include: package:flutter_lints/flutter.yaml analyzer: exclude: - test/**/*.mocks.dart - lib/config/firebase_options.dart linter: rules: - prefer_relative_imports - avoid_slow_async_io - cancel_subscriptions - close_sinks La línea del include es la que nos importa las opciones recomendadas por flutter, y sería a partir de la sección linter donde aparece la que en adición añadimos nosotros.\nPara que nos aplique los cambios de aquellas reglas que sí que tienen asociada un quick fix, podemos ejecutar el comando dart fix --apply \u0026lt;nombre_de_archivo\u0026gt;.\nLinter para .editorconfig El archivo \u0026ldquo;.editorconfig\u0026rdquo; es una herramienta que se utiliza en proyectos de código para establecer y mantener la consistencia en la configuración del editor. Permite definir reglas y configuraciones específicas, como el estilo de sangrado, la indentación, la codificación de caracteres y las preferencias de final de línea. Al emplearlo, se mejora la legibilidad del código, se reduce el conflicto en el control de versiones y se facilita la colaboración entre los miembros del equipo.\nQuisimos aplicarlo en adición al linter, ya que la cantidad de indentación o el ancho de línea eran cosas que no podíamos tener con el linter por defecto de Flutter.\nEn el título de este apartado he indicado “linter para el editorconfig” aunque como hemos visto, el editorconfig en sí no es un linter. Pero encontré una forma de que se comportara como tal. Gracias al uso de la herramienta editorconfig-checker, podía ejecutar el comando ec y este me arrojaría por consola el resultado de comprobar si se están cumpliendo las reglas del editorconfig en el proyecto.\nCambiemos solo lo necesario Ahora sabemos aplicar el linter de flutter y además usamos la herramienta editorconfig-checker para garantizar que nuestro código cumple los estándares que esperamos, pero… si ahora pretendemos añadirlo en nuestro proyecto tendrá que recorrer muchos archivos, nos arrojará muchas propuestas de cambios y será un poco dolor de cabeza que una sola persona tenga que arreglar eso, algo que le llevará mucho tiempo. Para solventar esto, lo que vamos a hacer es que solo tenga en cuenta lo que consideramos que son archivos en los que hemos trabajado. En nuestro caso, serían esos archivos que se van a incorporar al repositorio remoto y que están a la espera de ser subidos. Usando git lo haríamos tal que así:\nPrimero añadimos los archivos modificados al VCS\n1 git add -u flutter analyze + only changes\n1 2 echo \u0026#34;🔃 Running flutter analyze...\u0026#34; flutter analyze $(git diff --cached --name-only) ec-checker + only changes\n1 2 3 echo -e \u0026#34;\\n🔃 Running .editorconfig check...\u0026#34; ec $(git diff --cached --name-only) echo -e \u0026#34;\\n✅ .editorconfig check passed\u0026#34; auto-fix the output of flutter analyze + only changes\n1 git diff --cached --name-only | xargs -I {} dart fix --apply \u0026#34;{}\u0026#34; ¿Como automatizarlo? La gracia de definir unas herramientas que buscan conseguir que el equipo siga un estilo de código común, es utilizar la herramienta, por lo que no nos sirve que a veces si la usemos y otras no. A veces estamos pensando en muchas cosas, nos olvidamos del linter y es ahí donde empiezan las inconsistencias. Además de querer que se emplee la herramienta, también debemos creer que cuando añadimos herramientas a nuestro flujo de trabajo es importante que no supongan una carga para el equipo, en su lugar solo nos debería beneficiar.\nPor estas razones, lo mejor es tratar de automatizar la ejecución de las herramientas para que los desarrolladores nos podamos concentrar en la tarea que nos ocupa.\nHooks de Git Los hooks de Git son scripts personalizables que se pueden ejecutar automáticamente en ciertos puntos clave del ciclo de vida de Git. Estos scripts se encuentran en la carpeta \u0026ldquo;.git/hooks\u0026rdquo; de un repositorio de Git y se activan en respuesta a eventos específicos, como confirmar cambios, fusionar ramas, empujar cambios, entre otros.\nPara simplificarnos el proceso de ejecutar los linter, nos apoyaremos en dos hooks de Git. Primero usaremos el pre-commit para que antes de hacer commit dé los cambios:\npre-commit\n1 2 3 4 5 6 7 8 9 #!/bin/bash MODIFIED_FILES=($(git diff --cached --name-only --diff-filter=d)) if [[ ${MODIFIED_FILES[@]} =~ .dart$ ]]; then DART_FILES=($(git diff --cached --name-only --diff-filter=d | grep .dart)) dart format --line-length 150 \u0026#34;${DART_FILES[@]}\u0026#34; fi git add \u0026#34;${MODIFIED_FILES[@]}\u0026#34; exit 0 Esto nos garantiza que se cumple un ancho de línea determinado y que el código sigue unos estándares de formato definidos por Dart. Finalmente, usaremos el pre-push, para que antes de hacer push de los cambios al repositorio remoto, se realicen las comprobaciones de los linter pertinentes.\npre-push\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #!/bin/bash IS_SCRIPT_PASSED=0 CURRENT_BRANCH=$(git branch --show-current) MODIFIED_FILES=($(git diff --name-only --diff-filter=d \u0026#34;origin/$CURRENT_BRANCH\u0026#34; HEAD)) if [ ${#MODIFIED_FILES[@]} -eq 0 ]; then echo \u0026#34;✅ No versioned modified files tracked by the linter are pending to be pushed, skipping linter...\u0026#34; echo -e \u0026#34;\\n🚀 Proceeding with the push...\u0026#34; exit 0 fi echo -e \u0026#34;\\n🔃 Running flutter analyze...\u0026#34; flutter analyze \u0026#34;${MODIFIED_FILES[@]}\u0026#34; ANALYZE_EXIT_CODE=$? if [ $ANALYZE_EXIT_CODE -ne 0 ]; then echo -e \u0026#34;\\n❌ Flutter analyze failed. Please fix the issues before pushing\u0026#34; echo -e \u0026#34;Execute \\e[36mtask linter\\e[0m to list the warnings or execute \\e[36mtask linter-fix\\e[0m to automatically fix all of them\u0026#34; echo -e \u0026#34;\\e[33mRemember that maybe not all the warnings would be automatically fixed as they could haven\u0026#39;t a quick fix\\e[0m\u0026#34; IS_SCRIPT_PASSED=1 else echo -e \u0026#34;\\n✅ Flutter analyze passed\u0026#34; fi echo -e \u0026#34;\\n🔃 Running .editorconfig check...\u0026#34; ec \u0026#34;${MODIFIED_FILES[@]}\u0026#34; CHECK_EXIT_CODE=$? if [ $CHECK_EXIT_CODE -ne 0 ]; then echo -e \u0026#34;\\n❌ You have infringed some rules of the .editorconfig. Please fix the issues before pushing\u0026#34; echo -e \u0026#34;\\e[33mYou can reformat the code using your IDE but remember that some rules need to be fixed manually\\e[0m\u0026#34; IS_SCRIPT_PASSED=1 else echo -e \u0026#34;\\n✅ .editorconfig check passed\u0026#34; fi if [ $IS_SCRIPT_PASSED -ne 0 ]; then echo \u0026#34;\u0026#34; exit 1 fi echo -e \u0026#34;\\n🚀 Proceeding with the push...\u0026#34; exit 0 En el equipo decidimos dejar en el pre-commit algo que no fuera muy restrictivo, es decir, algo que no pudiese detener que hagamos commit, ya que si estamos trabajando con micro-commit nos parecía que podía suponer una carga. Es a la hora de subir los cambios utilizando el pre-push donde se ejecutan las revisiones más restrictivas, donde en caso de no seguir los estándares no se permite hacer push de los cambios, con el fin de cumplir los estándares de código acordados por el equipo.\nPipelines Además de tener los hooks de Git, es buena opción tener los linter como un paso de nuestra pipeline para que se ejecute en nuestra integración continua. Al final, los hooks de Git son archivos configurables que no son versionados, por lo que depende del buen hacer de cada persona tenerlos actualizados y respetar que se ejecuten, en lugar de configurar el IDE para que se salte las comprobaciones. Es en este escenario, donde la integración continua sí nos garantiza que el código que está listo para ser fusionado con la rama principal y cumple los estándares de los linter.\nEn ocasiones he escuchado opiniones de otros desarrolladores que opinan que incluir un linter en la pipeline es un lastre, porque puede suponer que tardes más en subir cambios, o porque resulta tedioso el hecho de querer subir cambios y tener que solventar esos conflictos primero. Yo creo que en un equipo es normal que entre las diferentes personas, haya distintas formas de cuidar el código, por eso usamos los linter, para conseguir un resultado uniforme que sea del agrado de todos. Por esto, no encuentro razones en los que hayan casos donde no tenga sentido seguir los estilos definidos, es más, si empezamos a hacer pequeñas excepciones solo para subir código más rápido, es cuando comienzan a surgir incongruencias en el código que darán más trabajo a los demás cuando se lo encuentren desarrollando alguna feature.\nConclusión Personalmente, es de los primeros linter que configuro y probablemente no tengo mucho bagaje en el asunto, por lo que me encantaría escuchar opiniones y sugerencias ¡Gracias por leer!\n","permalink":"https://raulpadilladelgado.github.io/blog/p/configurando-un-linter-en-flutter-y-otras-herramientas/","summary":"\u003ch2 id=\"qué-es-un-linter\"\u003e¿Qué es un \u003cem\u003elinter\u003c/em\u003e?\u003c/h2\u003e\n\u003cp\u003eLos \u003cem\u003elinter\u003c/em\u003e son herramientas esenciales en proyectos de código, ya que ayudan a mantener una base de código consistente y de alta calidad. Analizan el código en busca de posibles errores, violaciones de estilo y otros problemas, asegurando que el código siga las mejores prácticas y estándares de codificación acordados por el equipo. Al utilizar una herramienta de \u003cem\u003elinter\u003c/em\u003e, podemos detectar y solucionar problemas temprano, lo que resulta en un código más limpio y un proceso de desarrollo más fluido. Con la capacidad de aplicar convenciones de codificación, identificar posibles errores y mejorar la legibilidad del código, una herramienta de \u003cem\u003elinter\u003c/em\u003e es un activo valioso para cualquier proyecto de código. Al mantener un código consistente y de alta calidad, se facilita la colaboración entre los miembros del equipo, se reducen los conflictos y se mejora la eficiencia en el desarrollo.\u003c/p\u003e","title":"Configurando un linter en flutter y otras herramientas"},{"content":"Introducción Típicamente, cuando estamos desarrollando código, hacemos mucho uso de la terminal para ejecutar distintos comandos que nos permiten, por ejemplo, comprobar que nuestros test pasan, descargar dependencias, arrancar la aplicación en local\u0026hellip; Estos comandos suelen ser largos y nos puede resultar difícil recordarlos o simplemente tenemos que ejecutar varios porque encadenan una función que queremos realizar sobre nuestro código.\nEs aquí donde nos preguntamos si existía alguna herramienta que nos simplificara esta tarea y que fuera fácil de implementar y usar. La primera que se nos ocurrió debido a su popularidad fue Makefile. Indagando un poco más sobre qué es esta tecnología y para qué fue creada, llegamos a la conclusión de que su propósito principal es describir las dependencias entre los archivos fuente de un proyecto y las reglas para compilarlos, así como generar ejecutables. Esta definición no cuadraba mucho con el tipo de herramienta que estábamos buscando, por lo que decidimos descartarla.\nTras el punto anterior, seguimos con nuestra investigación y consultamos a compañeros. Varios nos recomendaron una herramienta llamada Task que por ahora suplía todas nuestras necesidades.\n¿Qué es Task? La herramienta se describe a sí misma como un runner de tareas o una herramienta de construcción que busca ser más simple y fácil de usar que GNU Make. Como principales ventajas frente a la herramienta con la que se comparan, tenemos que:\nEs una herramienta multiplataforma. Make funciona completamente en las máquinas Unix, pero en Windows hay que hacer algún cambalache como usar Cygwin o WSL para poder usarlo. Para Task, con la instalación ya es totalmente compatible con tu sistema operativo. Tiene una sintaxis más simple. Por lo tanto, es más sencillo entenderlo y aplicarlo que Make. Además, los IDE modernos, como IntelliJ o Visual Studio Code, tienen soporte para sugerirte qué sintaxis puedes usar con su debida documentación embebida. Tiene muchas más funcionalidades. Como comentamos anteriormente, Make no se pensó como un runner de tareas, por lo que entendemos que no se buscaba tener tantas funcionalidades como tiene Task a la hora de encadenar tareas, definir sus descripciones, entre otras. Task por defecto buscará en tu proyecto un archivo llamado Taskfile.yml que podría tener una estructura como esta:\n1 2 3 4 5 6 7 version: \u0026#39;3\u0026#39; tasks: hello: cmds: - echo \u0026#39;Hello World from Task!\u0026#39; silent: true Algo a remarcar es que no busca un Taskfile.yml en la ruta raíz de tu proyecto, aunque típicamente el archivo se suela ubicar ahí. En su lugar, es capaz de buscar recursivamente por las carpetas de tu proyecto por ese archivo. Gracias a esto no te tienes que preocupar de en qué localización de tu proyecto te encuentras para lanzar una tarea con Task.\nEjemplo Les mostramos un breve ejemplo propio sobre como se puede usar la herramienta:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 version: \u0026#39;3\u0026#39; silent: true tasks: test: desc: \u0026#34;🧪 Run tests\u0026#34; cmds: - flutter test build: desc: \u0026#34;🔨 Build application executable\u0026#34; preconditions: - sh: ls {{ .TASKFILE_DIR }} msg: \u0026#34;{{.PRECONDITION_MESSAGE}}\u0026#34; cmds: - task: my-plugin:generate - task: build-to-macos build-to-macos: internal: true platforms: [ darwin ] cmds: - flutter build macos --release default: cmds: - task -l vars: PRECONDITION_MESSAGE: 🤷‍♀️You need to have a valid Taskfile in your myplugin project MY_PLUGIN_DIR: ../my-plugin TASKFILE_DIR: \u0026#34;{{ .MY_PLUGIN_DIR }}/Taskfile.yml\u0026#34; includes: my-plugin: taskfile: \u0026#34;{{ .MY_PLUGIN_DIR }}\u0026#34; dir: \u0026#34;{{ .MY_PLUGIN_DIR }}\u0026#34; internal: true optional: true La tarea build construye el ejecutable de una aplicación y se apoya en una tarea de otro Taskfile, estableciendo una precondición que verifica que exista el fichero, y luego utiliza una tarea interna que solo se ejecuta en caso de que tu sistema operativo sea MacOS.\nComo se puede ver, se extraen variables para simplificar y reducir la cantidad de texto literal, ya sea rutas u otros textos complejos.\nLa tarea default es un truco que hemos hecho para que simplemente con ejecutar el comando task sin ningún argumento, te liste las tareas disponibles, lo que normalmente tendrías que hacer ejecutando task -l. Esta seria su salida:\n❯ task task: Available tasks for this project: * build: 🔨 Build application executable * test: 🧪 Run tests Por defecto, cuando ejecutamos task -l se usa una ordenación alfanumérica, por lo que si quieres que se muestren por orden de tu preferencia podrías cambiar la tarea default para que use el comando task -l -sort none. Esto provoca que a la hora de mostrarlos sigan el mismo orden que definiste en el Taskfile.\nNuestra experiencia En apenas unas pocas horas logramos implementarlo en un proyecto real. Una de las primeras cosas que notamos fue su gran escalabilidad. Con Task, puedes cubrir el espectro completo de las tareas, desde las más simples, como definir un alias para un comando largo, hasta las más complejas, como encadenar diversas operaciones y ejecutarlas según tu sistema operativo.\nUn elemento muy simple, pero que nos resulta muy útil, es la posibilidad de ejecutar un comando que muestra todas las tareas configuradas junto con su descripción. Esto agrega una capa de organización y claridad a nuestro flujo de trabajo que simplemente no estaba presente antes.\nEn el uso que hemos dado a la herramienta, hay varias características poderosas que queremos destacar:\nTaskfile te permite especificar que ciertas tareas se ejecuten en un directorio en particular. Esto es extremadamente útil para mantener separadas las tareas y los contextos en los que se ejecutan. Las precondiciones, que verifican si la tarea necesita ser ejecutada o no, han resultado ser una herramienta valiosa para prevenir la redundancia y optimizar el tiempo de trabajo. La habilidad de conectar múltiples Taskfiles es otra función que hemos explotado bastante. Puedes importar un Taskfile dentro de otro y usar sus tareas, lo que facilita enormemente la reutilización de código y la organización. Nos ha sido muy útil tener tareas que se definen como internas, que no se ejecutan desde fuera, sino que son llamadas por otras tareas que sí son públicas. Esta funcionalidad facilita la extracción de métodos, de la misma manera que lo harías en un lenguaje de programación. La opción de definir varios alias para la misma tarea es otra función que hemos disfrutado. Cada persona puede invocar la tarea con el nombre de su elección, lo que ayuda a la personalización y la comodidad del usuario. Finalmente, la gestión de errores en Task es sólida y eficiente, permitiéndonos identificar y solucionar problemas con mayor rapidez. En resumen, nuestra experiencia con Taskfile ha sido muy positiva. Ha mejorado nuestra eficiencia, organización, y nos ha proporcionado un conjunto de herramientas poderosas para manejar nuestras tareas de desarrollo. Sin duda, lo recomendamos a otros equipos de desarrollo que busquen una herramienta de gestión de tareas sólida y flexible.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/automatizando-tareas-con-task/","summary":"\u003ch2 id=\"introducción\"\u003eIntroducción\u003c/h2\u003e\n\u003cp\u003eTípicamente, cuando estamos desarrollando código, hacemos mucho uso de la terminal para ejecutar distintos comandos que nos permiten, por ejemplo, comprobar que nuestros \u003cem\u003etest\u003c/em\u003e pasan, descargar dependencias, arrancar la aplicación en local\u0026hellip; Estos comandos suelen ser largos y nos puede resultar difícil recordarlos o simplemente tenemos que ejecutar varios porque encadenan una función que queremos realizar sobre nuestro código.\u003c/p\u003e\n\u003cp\u003eEs aquí donde nos preguntamos si existía alguna herramienta que nos simplificara esta tarea y que fuera fácil de implementar y usar. La primera que se nos ocurrió debido a su popularidad fue Makefile. Indagando un poco más sobre qué es esta tecnología y para qué fue creada, llegamos a la conclusión de que su propósito principal es describir las dependencias entre los archivos fuente de un proyecto y las reglas para compilarlos, así como generar ejecutables. Esta definición no cuadraba mucho con el tipo de herramienta que estábamos buscando, por lo que decidimos descartarla.\u003c/p\u003e","title":"Automatizando tareas con Task"},{"content":"Introducción Los lenguajes de programación y los frameworks evolucionan con el tiempo para ofrecer a los desarrolladores una forma más simple y eficaz de resolver los problemas del mundo real y conseguir una solución adecuada según el contexto. Por ejemplo, cuando nació la programación orientada a objetos se pudo romper la barrera entre desarrollador y experto de producto plasmando la problemática en objetos de dominio que actuaban como lenguaje común para ambas partes.\nOtra gran necesidad que surgió fue la de no utilizar el clásico modelo de programación «instrucción por instrucción», donde es necesario que para poder pasar a la siguiente instrucción, la anterior haya finalizado su propósito. En su lugar, se busca un planteamiento asíncrono que permita aprovechar al máximo los hilos de ejecución de nuestra aplicación para brindarle al usuario una experiencia más rápida y flexible. Es aquí donde aparece el concepto de Programación Reactiva.\nCabe destacar que se originó en la década de 1990 con el concepto de programación funcional reactiva. Científicos de la computación como David Pearce, Conal Elliott y Paul Hudak, trabajaron en el desarrollo de lenguajes y teorías que dieron lugar a la programación reactiva tal como la conocemos hoy en día.\nEn 2003, un grupo de desarrolladores liderados por Anders Hejlsberg en Microsoft, crearon Reactive Extensions (Rx), una biblioteca para programación reactiva en .NET. Fue ahí que comenzó a ganar mucha popularidad y fue siendo adoptada en muchos otros lenguajes y frameworks.\n¿Qué es la programación reactiva? La programación reactiva es un paradigma de programación que se centra en la gestión de datos que cambian a lo largo del tiempo. En lugar de tratar los datos como valores estáticos, se tratan como flujos de datos que cambian en el tiempo.\nUn programa reactivo se basa en la idea de que los datos pueden ser tratados como un flujo continuo de eventos que se pueden procesar de forma asíncrona. Los programas reactivos pueden manejar grandes cantidades de datos y son adecuados para aplicaciones que necesitan procesar grandes cantidades de información de manera eficiente, como aplicaciones en tiempo real o aplicaciones que manejan mucho tráfico de red.\nUn bloque de código se subscribe a un flujo de datos continuo definido por otro código para comenzar a recibir notificaciones (datos) cuando se producen cambios en los datos. Gracias a esto, nuestro código se vuelve asíncrono y no bloqueante, permitiendo que un hilo de ejecución no se bloquee por estar ejecutando una instrucción que depende de recibir una serie de datos y que delegue el trabajo a otro hilo para poder seguir procesando otras instrucciones, y este hilo a su vez no depende de recibir los datos en el momento, si no que es capaz de subscribirse a la entrada de esos datos para ir recibiéndolos a medida que están listos.\nAdemás de la asincronía, otra gran ventaja que nos brinda este paradigma es la posibilidad de ser más resiliente a los fallos, o diferentes enfoques para evitarlos. Por ejemplo, si tenemos que consumir una gran cantidad de datos desde una fuente pero sabemos que si cargamos todos de golpe nuestro sistema se va a sobrecargar y por tanto dejará de funcionar, podemos poner límites ya sea por lotes en los que consumimos poco a poco todos los datos, o incluso controlando la carga por tiempo, cargando solo durante un tiempo y reanudando después. Hay muchas posibilidades disponibles para usar según sea el problema que tengamos que resolver.\nEjemplo (Spring Boot + Kotlin) El código fuente de los siguientes ejemplos lo puedes encontrar alojado en Github visitando el siguiente enlace:\nhttps://github.com/raulpadilladelgado/reactive-springboot\nPublicar API Rest Reactiva Nuestra aplicación Spring Boot será reactiva gracias al uso de la librería spring-boot-starter-webflux, ya que nos permite escribir y leer REST APIs reactivas. Para que podamos relacionar el concepto con algo más tangible vamos a ver un ejemplo de programación reactiva usando Spring Boot y Kotlin.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE import reactor.core.publisher.Flux import reactor.core.publisher.Mono import java.time.Duration.ofSeconds @RestController class SongController { @Autowired private lateinit var songRepository: SongRepository @GetMapping(value = [\u0026#34;/song\u0026#34;], produces = [TEXT_EVENT_STREAM_VALUE]) fun findSongs(): Flux\u0026lt;Song\u0026gt; { return songRepository.findAll().delayElements(ofSeconds(3)) } @PostMapping(value = [\u0026#34;/song\u0026#34;], produces = [TEXT_EVENT_STREAM_VALUE]) fun saveSong(@RequestBody song: Song): Mono\u0026lt;Song\u0026gt; { return songRepository.save(song) } } Para habilitar la programación reactiva en el primer endpoint, hemos usado el tipo de retorno Flux de la biblioteca Reactor de Spring. Este tipo representa un flujo de datos asincrónico que puede emitir varios valores a lo largo del tiempo. Además, hemos especificado que la API debe producir un tipo de contenido text/event-stream, lo que indica que se trata de un flujo de eventos en tiempo real. Para simular que nuestra API está tratando de consumir datos desde una fuente que puede tardar más tiempo del deseado, hemos usado delayElements para retrasar un poco el envío de datos desde nuestro endpoint.\nPara que nuestra API sea totalmente reactiva, cabe destacar que hemos usado la librería R2DBC, más concretamente en su implementación para PostgreSQL, la cual nos permite operar con la base de datos de una forma reactiva trabajando con los ya comentados flujos de datos. La ventaja frente a JDBC (librería más común para persistencia en el mundillo de Spring Boot) es que los métodos que nos ofrece para operar con la base de datos no son bloqueantes y nos facilita la integración con nuestra API reactiva ya que trabaja usando los tipos de datos asíncronos como Flux o Mono. Vamos a ver la implementación del repositorio:\n1 2 3 4 5 6 ... //R2DBC PostgreSQL import org . springframework . data . r2dbc . repository . R2dbcRepository @Repository interface SongRepository : R2dbcRepository\u0026lt;Song, Int\u0026gt; Al extender de ReactiveCrudRepository ya tenemos disponibles una gran cantidad de métodos como find, findAll, Save… (véase el ejemplo en el código del controlador más arriba).\nEn el segundo endpoint, hemos usado el tipo Mono, que solo contiene un valor y que usamos para retornar el elemento que guardamos en la base de datos.\nOtro aspecto importante a tener en cuenta es cómo configuramos SpringBoot para que pueda conectarse a nuestra base de datos. Tenemos que especificar qué URL tiene que usar R2DBC para conectarse:\nInserta este código en tu application.yml o application.properties:\n1 2 3 4 5 spring: r2dbc: url: r2dbc:postgresql://postgres:5432/admin username: admin password: password Esta configuración será variable dependiendo de como tengas desplegada tu base de datos. En el ejemplo que proporciono, la base de datos se arranca en local junto con la aplicación utilizando Docker.\nConsumir API Rest Reactiva Aquí te presento un ejemplo de cómo consumir de forma reactiva esta API desde una aplicación cliente usando la biblioteca Reactor de Spring:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 ... import org . springframework . web . reactive . function . client . WebClient import java . time . Duration @RestController class ConsumeSongController { @GetMapping(\u0026#34;/consume\u0026#34;) fun consumeAll() { val response = WebClient.builder() .baseUrl(SONGS_URL) .build() .get() .retrieve() .bodyToFlux(Map::class.java) response.subscribe { song -\u0026gt; println(song) } } @GetMapping(\u0026#34;/consume-only-two\u0026#34;) fun consumeOnlyTwo() { val response = WebClient.builder() .baseUrl(SONGS_URL) .build() .get() .retrieve() .bodyToFlux(Map::class.java) response.take(2).subscribe { song -\u0026gt; println(song) } } @GetMapping(\u0026#34;/consume-for-five-seconds\u0026#34;) fun consumeForFiveSeconds() { val response = WebClient.builder() .baseUrl(SONGS_URL) .build() .get() .retrieve() .bodyToFlux(Map::class.java) response.take(Duration.ofSeconds(5)).subscribe { song -\u0026gt; println(song) } } } private const val SONGS_URL = \u0026#34;http://127.0.0.1:8080/song\u0026#34; Para recibir la respuesta como un flujo de eventos, hemos usado el método bodyToFlux de la clase WebClient ( librería reactiva para consumir APIs), que nos permite almacenar los datos a medida que llegan en la variable. Finalmente, hemos suscrito un manejador al flujo de eventos, que simplemente imprime cada evento recibido por consola. Al ejecutar este código, la aplicación cliente comenzará a consumir el flujo de eventos de forma reactiva y se imprimirá un mensaje por cada evento recibido.\nComo comentamos anteriormente, tenemos muchas posibilidades para evitar la sobrecarga en nuestra aplicación especificando cada cuanto tiempo queremos coger datos o cuanta cantidad, etc.\nWebgrafía Jacquot, A. (8 de julio del 2021). Programación reactiva con Spring Data R2DBC. Blog de Pictet Technologies: https://medium.com/pictet-technologies-blog/reactive-programming-with-spring-data-r2dbc-ee9f1c24848b\nPaluch, M. (7 de diciembre de 2018). Programación Reactiva y Bases de Datos Relacionales. Blog de primavera: https://spring.io/blog/2018/12/07/reactive-programming-and-relational-databases\nVijay, Srj. (10 de junio del 2022). ¿Cómo implementar la programación reactiva en Spring Boot?. El desarrollador de la pila completa: https://fullstackdeveloper.guru/2022/06/10/how-to-implement-reactive-programming-in-spring-boot/\n","permalink":"https://raulpadilladelgado.github.io/blog/p/programaci%C3%B3n-reactiva-que-es-y-como-usarla-en-spring-boot/","summary":"\u003ch2 id=\"introducción\"\u003eIntroducción\u003c/h2\u003e\n\u003cp\u003eLos lenguajes de programación y los \u003cem\u003eframeworks\u003c/em\u003e evolucionan con el tiempo para ofrecer a los desarrolladores una forma\nmás simple y eficaz de resolver los problemas del mundo real y conseguir una solución adecuada según el contexto. Por\nejemplo, cuando nació la programación orientada a objetos se pudo romper la barrera entre desarrollador y experto de\nproducto plasmando la problemática en objetos de dominio que actuaban como lenguaje común para ambas partes.\u003c/p\u003e","title":"Programación reactiva, que es y como usarla en Spring Boot"},{"content":"Introducción Creo que es una realidad imperativa la relevancia de mantener actualizadas las versiones que utilizamos de librerías/dependencias externas en nuestros proyectos. Ya sea porque evitamos brechas de seguridad que se propagan desde ese factor externo hasta nuestro código o también incluso porque las pequeñas actualizaciones son más fáciles de incorporar. Cambiar algunas grandes versiones más adelante, junto con las complicaciones que esto conlleva, puede hacer que sea un auténtico sufrimiento tener que adaptar nuestro código a los nuevos mandatos de la dependencia externa, ya que han ocurrido cambios que rompen con lo que actualmente usábamos.\nPartiendo de esta premisa, en la que no le estoy abriendo los ojos a nadie, me gustaría compartir una herramienta en la que he trabajado para solventar una necesidad que tenía en el día a día con mi equipo de trabajo y que puede ser útil para cualquier proyecto que busque una solución similar.\nTodos nuestros proyectos están escritos en Java o Kotlin usando como framework Spring Boot y Gradle como herramientas de compilación. En Gradle las dependencias se definen con un archivo build.gradle, que puede tener una estructura como la siguiente:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 plugins { id \u0026#39;org.springframework.boot\u0026#39; version \u0026#39;2.7.4\u0026#39; id \u0026#39;io.spring.dependency-management\u0026#39; version \u0026#39;1.0.14.RELEASE\u0026#39; id \u0026#39;java\u0026#39; id \u0026#34;com.github.ben-manes.versions\u0026#34; version \u0026#34;0.43.0\u0026#34; } group = \u0026#39;com.example\u0026#39; version = \u0026#39;0.0.1-SNAPSHOT\u0026#39; sourceCompatibility = \u0026#39;17\u0026#39; repositories { mavenCentral() } dependencies { testImplementation \u0026#39;org.junit.jupiter:junit-jupiter-api:5.8.2\u0026#39; testImplementation \u0026#39;org.junit.jupiter:junit-jupiter-params:5.8.2\u0026#39; testImplementation \u0026#39;org.assertj:assertj-core:3.22.0\u0026#39; testImplementation \u0026#39;org.mockito:mockito-core:4.5.1\u0026#39; testRuntimeOnly \u0026#39;org.junit.jupiter:junit-jupiter-engine:5.8.2\u0026#39; } tasks.named(\u0026#39;test\u0026#39;) { useJUnitPlatform() } Problemática Para mantener nuestras dependencias actualizadas recurrimos a un plugin que tanto por consola como en un fichero de texto plano nos muestra las nuevas versiones a las que podemos actualizar las nuestras. Dicho plugin lo hemos visto en el anterior bloque de código id \u0026quot;com.github.ben-manes.versions\u0026quot; version \u0026quot;0.43.0\u0026quot; y este nos permite ejecutar el comando desde la consola ./gradlew dependencyUpdates.\nEs aquí donde surge nuestro «problema», y es que como hemos visto, debemos puntualizar o precisar el lanzamiento de dicho comando en local para saber si tenemos que actualizar algo y a qué versión en concreto, por lo que depende de la buena memoria de los developers a la hora de lanzar esto periódicamente y realizar los cambios pertinentes. Es entonces cuando se me ocurre que quizá esto se pueda automatizar aprovechando el CI/CD, por lo que me puse a buscar soluciones ya existentes. Para mi asombro, fue realmente simple encontrarlas en el marketplace de Github Actions para este propósito, pero no terminaban de convencerme. Resulta que todo lo que encontré eran soluciones en las que automáticamente se abría una Pull Request en el proyecto con las dependencias actualizadas para que los developers puedan mergearla, e incluso otras soluciones eran auto-mergeable ¿Suena bien verdad? Ahora bien, planteándome la situación en profundidad descubrí que esto no terminaba de ajustarse a nuestra forma de trabajo, ya que implicaría una revisión constante, estar pendiente de la sección de PR en los proyectos, por lo que si contamos con un sistema de microservicios este razonamiento nos suponía que era inviable. En ocasiones, los grandes cambios entre versiones de las dependencias puede conllevar que nuestros test no pasen en verde, lo que requiere de un trabajo extra, así que tuve que descartar las soluciones auto-mergeable. Mi impresión era que nos sería más sencillo y flexible a todos decidir en las Pull Request que haríamos con las actualizaciones pendientes que teníamos, teniendo la posibilidad de no actualizar algunas porque harían que algunos test no pasaran en verde o por cualquier otro factor que nos pudiese suponer un bloqueo para sacar la Pull Request adelante en un momento en el que quizá no podíamos permitirnos dedicarle demasiado tiempo.\nSolución Una vez identificada la problemática, me gustaría contaros la resolución a la que llegué. Básicamente, he desarrollado una Github Action que he publicado en el Marketplace de Github, donde te comenta las PR que abras con las dependencias que necesitas actualizar, o en su defecto con un comentario de que todo está actualizado. El código fuente de la action lo podéis encontrar aquí. Os pongo también dos GIF sobre como funciona:\nCuando tienes dependencias que actualizar: Cuando todas tus dependencias están actualizadas: Un poco más de Github Actions Además de compartir esta herramienta, me gustaría haceros una breve introducción para animaros a desarrollar vuestra propia Github Action alguna vez. Lo primero de todo es consultar la información más extendida en la documentación oficial de Github, sin embargo, trataré de resumir lo básico que necesité para crear la mía.\nExisten tres formas de crear GitHub Actions:\nUsando Javascript Usando Docker Compuestas En mi caso he optado por crear una acción que funciona dentro de un contenedor de Docker. El proyecto se compone de:\nUn archivo Dockerfile en el cual se define la imagen sobre la que se ejecutará el script bash que declaramos en el entrypoint. Un archivo de metadatos en el que se definen las entradas y salidas de la action así como otra información relevante para el marketplace de Github. El script de bash en el que se encuentra el core de nuestra action. Conclusión En un mundo ideal no debería ser necesario esta «manualidad» de tener que actualizar a mano las dependencias, pero hasta que conozca una solución cómoda y simple creo que esta forma de recordar que existen actualizaciones pendientes en las PR es una buena forma de no descuidar las dependencias de un proyecto Gradle.\nEspero que este post haya sido claro y preciso, a la vez que útil e informativo. ¡Gracias por dedicar una parte de vuestro tiempo a la lectura de este artículo!\n","permalink":"https://raulpadilladelgado.github.io/blog/p/comprobar-versiones-de-dependencias-de-gradle-en-las-pull-request/","summary":"\u003ch2 id=\"introducción\"\u003eIntroducción\u003c/h2\u003e\n\u003cp\u003eCreo que es una realidad imperativa la relevancia de mantener actualizadas las versiones que utilizamos de\nlibrerías/dependencias externas en nuestros proyectos. Ya sea porque evitamos brechas de seguridad que se propagan desde\nese factor externo hasta nuestro código o también incluso porque las pequeñas actualizaciones son más fáciles de\nincorporar.\nCambiar algunas grandes versiones más adelante, junto con las complicaciones que esto conlleva, puede hacer que sea un\nauténtico sufrimiento tener que adaptar nuestro código a los nuevos mandatos de la dependencia externa, ya que han\nocurrido cambios que rompen con lo que actualmente usábamos.\u003c/p\u003e","title":"Comprobar versiones de dependencias de Gradle en las Pull Request"},{"content":"Introducción Toda la información que encontrarás aquí está basada en el libro de Carlos Blé \u0026ldquo;Código sostenible\u0026rdquo;. En este post hablaré sobre lo que he aprendido con la lectura de este libro. Serán unas breves nociones lo que destacaré, por lo que para obtener la mejor experiencia te recomiendo que leas el libro original, te será muy útil el detalle con el que se explican los conceptos y los ejemplos usados en esta guía para realizar código limpio y fácil de mantener.\n¿Qué es código sostenible? El código debe ser funcionalmente perfecto, debe cumplir todos los criterios especificados, pero además debe ser fácil de entender, mantener y escalar.\nSi tu código no es sostenible será una principal fuente de fracaso en tu empresa, ya que con el tiempo será cada vez más difícil de modificar.\nY no confundamos que sostenible va relacionado con futurista, ya que precisamente intentar realizar código que cubra los escenarios de hoy, pero también los de mañana, complica mucho el trabajo que tenemos que hacer y en la mayoría de ocasiones nos induce a que no podamos tener un código sostenible.\nEn resumen, el código es sostenible cuando es simple y conciso, y está respaldado por test automáticos.\n¿Cómo consigo un código sostenible? Como vimos en el punto anterior, tenemos que lograr un código sencillo y concreto. Para entender cómo logramos esto, veamos qué está en nuestra mano.\nNo caigas en la reutilización de código excesiva sin control que, por ejemplo, intenta hacer que una misma función sirva para todo, ya que esto hará que uses menos abstracciones. Las abstracciones son la semántica de tu aplicación. Cada vez que extraes un objeto o una función y le das un nombre estás añadiendo una abstracción que define la misión de esa parte del código. El código es mucho más sencillo de entender cuando es rico en el uso de lenguaje humano. Es por eso que debemos cuidar los nombres que le asignamos a las variables, métodos y clases, ya que determinarán la dificultad para entender tu código. Buscamos nombres coherentes con el contexto, pronunciables, que denoten claramente su comportamiento.\nComo bien indica el concepto anterior, crear abstracciones aporta semántica a nuestra aplicación y denota la intención de lo que programamos, podemos destacar también evitar caer en primitive obsession, la cual nos habla de evitar el uso excesivo de tipos primitivos (como String, Integer\u0026hellip;) y en su lugar favorecer nuestros tipos propios, que serían esas clases que envuelven a los primitivos y les da una misión y un sentido, ya que tendrán un contexto basado en el nombre de la clase y el comportamiento (métodos) estará guiado por el mismo contexto. La utilización de tipos propios ayuda a que podamos desarrollar esa nueva feature más rápido, porque con su utilización conseguimos un código fácil de comprender e intuitivo.\nEl código que usa correctamente los tipos propios habla el lenguaje del negocio para el que se está desarrollando la aplicación, es decir, un lenguaje común para todos en la empresa, y favorecerá la comunicación entre desarrolladores y otros cargos de negocio. Por supuesto evita los modelos anémicos, que serían aquellos objetos que no tienen datos o que los tienen, pero no los utilizan directamente porque exponen getters y setters y dejan que los demás artefactos decidan qué debe hacer. En su lugar, pídele al objeto que es el que tiene la responsabilidad de resolver ese problema que haga lo que quieres hacer, favorece el principio Tell don\u0026rsquo;t ask.\nPara tener un código simple, un buen ejemplo a seguir serían las cuatro reglas del diseño simple definidas por Kent Beck:\nPasar los test Revelar la intención No contener duplicidad (no repetir la implementación de un concepto) Tener el menor número de elementos posible (no hacer más de lo necesario) Procura diseñar software con alta cohesión y bajo acoplamiento, es decir, busca crear artefactos independientes, que no dependan de otros para funcionar. Siempre existirá un mínimo de acoplamiento, ya que hay que conectar las distintas piezas de nuestro código, pero evitar el acoplamiento excesivo hará escalable nuestro código. Implementar una arquitectura hexagonal puede ser muy beneficioso para que el dominio de nuestra aplicación no tenga dependencias externas. La ley de demeter también puede ser aplicada para evitar el acoplamiento. Para diseñar aplicaciones con alta cohesión y bajo acoplamiento es recomendable conocer los principios SOLID. También deberíamos limitar la visibilidad de métodos y clases, deberíamos ser conscientes de que aquello que marcamos como público, solo debería ser lo realmente necesario.\nEl código atractivo nos llama y nos resulta placentero a la hora de leerlo. Indentaciones bien realizadas, saltos de líneas justos, mismo nivel de abstracción del código dentro de un mismo bloque, y más, son ejemplos de lo que podemos hacer para que nuestro código se vea bonito y anime a interpretarlo. Usar cláusulas guarda es tan beneficioso para el rendimiento como para el aspecto visual, ya que denota claramente la intención de un método para forzar la detención de su ejecución y devolver algo cuando se da alguna condición.\nEvita las sorpresas, piensa detenidamente cada decisión de diseño que tomes, ya que tendrá un impacto en la persona que interprete después tu código, que podrías ser tú mismo. Ten constructores que solo construyen, es decir, que no ejecuten ninguna lógica compleja más allá de crear una instancia de la clase, si necesitas hacer validaciones delégalas a métodos de factoría y cierra la visibilidad del constructor. Favorece las funciones puras, aquellas que siempre retornan el mismo resultado para la misma entrada. Favorece el uso de las funciones propias de lenguaje, que en el caso de lenguajes modernos o de los más veteranos, pero que suelen traer mejoras con cada actualización, muchas veces incluyen funcionalidades que facilitan la vida del programador y reducen la cantidad de trabajo que tiene que realizar para resolver un problema, al mismo tiempo que reducen la probabilidad de introducir un error, ya que evitan la complejidad accidental incluida por el programador. Si trabajas con lenguajes modernos aprovecha todas sus ventajas y no caigas en viejos principios divulgados por limitaciones del lenguaje o porque simplemente responden a un contexto del software en una época donde lo correcto era eso, pero como en todo, siempre hay una forma de mejorar y surgen nuevos principios y patrones. Por ejemplo, hoy en día no se recomiendan que las funciones solo deban tener un único return. En realidad, si sabemos que nuestra función ya tiene un valor para retornar y esperamos hasta el final de la ejecución para retornarlo, nos estamos arriesgando a que mute su valor por el camino y no tengamos el resultado esperado, además aumentamos la probabilidad de un bug sin necesidad, ya que se ejecutan más líneas para obtener el mismo resultado. En su lugar, utilizar un return temprano será mucho más claro para los que lean el código y será más seguro, un concepto que se conoce como cláusula guarda. Como este hay muchos otros principios que no aplican para el desarrollo de software más habitual, solo es cuestión de detectarlos y aplicar la lógica, y como no, nuestros conocimientos de código sostenible, para evitarlos y tener un código más fácil de interpretar y de manejar.\nAunque tengamos una buena batería de test es necesario tener también un buen manejo y prevención de errores, algo que ocurre cuando tenemos en cuenta las posibles excepciones de nuestra aplicación y las manejas de una forma inteligente, notificándolas en un log de errores u otros sistemas de monitorización. Cuanto más entiendes el lenguaje de programación que está usando es más complicado que puedas introducir un error, ya que eres más consciente de cada cosa que realizas y qué riesgos puede tener. Utilizar un IDE para programar es lo mejor que puedes hacer, te resaltan zonas donde puedes mejorar el código o zonas donde puede haber riesgo de error, además te autocompletan código o lo sugieren para esos momentos en los que no tienes claro algo o simplemente te sirve para aumentar tu productividad. Para poder decir que tenemos una buena batería de test es necesario que pensemos en multitud de casos, todos los que se nos puedan ocurrir, para poder cubrir todos los escenarios posibles y que tengamos un código resiliente. Para evitar el uso de excepciones a lo loco, tenemos disponibles lenguajes null safe como Kotlin, pero si tú necesitas Java, por ejemplo, siempre tienes herramientas para usar en lugar de las excepciones, como los tipos Either y Try o el patrón notificación. Captúralas y envuélvelas en las tuyas propias para poder proporcionar información extendida y dar más contexto de por qué se pudo dar ese error. Nunca añadas un \u0026rsquo;throws\u0026rsquo; a la firma de tus métodos, es ignorar totalmente la excepción y no tiene sentido.\nCuando queremos código sostenible no todo es código, ya que hay que cuidar otros aspectos que mejoran la calidad de nuestro trabajo, como puede ser la posibilidad de replicar los entornos productivos en uno local para poder solucionar rápidamente los bugs, hacer pairing para resolverlos, o incluso como forma de trabajo estándar, por el hecho de que dos cabezas pueden pensar una solución mejor y estudiar los riesgos minuciosamente.\nConclusión Hacer código sostenible no es una tarea compleja, pero tampoco es fácil, es el fruto del cuidado diario de nuestro código tomando buenas decisiones en cada momento, huyendo de la complejidad y la sorpresa. Por experiencia propia, lo realmente complicado es no seguir las guías de código sostenible y tener que mantener ese código, es un quebradero de cabeza más grande que invertir nuestros esfuerzos en la aplicación de código sostenible. En definitiva, la fórmula perfecta para que un código sea mantenible, legible y escalable pasa por tener test suficientes y de calidad que cubran el código productivo, y que tanto los test como el código productivo sigan estos consejos que hemos aprendido con la lectura del libro y de este post.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/c%C3%B3digo-sostenible/","summary":"\u003ch2 id=\"introducción\"\u003eIntroducción\u003c/h2\u003e\n\u003cp\u003eToda la información que encontrarás aquí está basada en el libro de Carlos Blé\n\u003ca href=\"https://codigosostenible.com/\"\u003e\u0026ldquo;Código sostenible\u0026rdquo;\u003c/a\u003e. En este post hablaré sobre lo que he aprendido con la lectura de este\nlibro. Serán unas breves nociones lo que destacaré, por lo que para obtener la mejor experiencia te recomiendo que leas\nel libro original, te será muy útil el detalle con el que se explican los conceptos y los ejemplos usados en esta guía\npara realizar código limpio y fácil de mantener.\u003c/p\u003e","title":"Código sostenible"},{"content":"Introducción Consumer driven contract testing es un tipo de prueba que nos garantiza que un proveedor cumple lo acordado con un consumidor. Por ejemplo, en una API usaríamos este tipo de pruebas para asegurar que el proveedor recibe una petición y devuelve la respuesta esperada.\nCuando dos artefactos se comunican entre sí, típicamente lo suelen hacer mediante un mensaje, el cual tiene un formato determinado. Ese formato hace que el consumidor dependa del productor para poder funcionar correctamente, ya que su código se ajusta a un determinado formato de mensaje. Ese «acuerdo» entre ambas partes es lo que se denomina «contrato». Es entonces gracias a contract testing cuando podemos testear esa integración y asegurarnos que el mensaje que se envía de un artefacto a otro tiene el formato acordado para que la integración entre ambos funcione a la perfección.\nPero más allá de ser una herramienta para testear la integración entre varios artefactos, también lo podemos usar en el contexto de un único artefacto. Véase el caso de asegurar que una API cumple el formato acordado en su documentación, pero en este caso sin hacer el enfoque en un tipo de consumidor concreto.\nHablamos de una herramienta que nos permite testear cualquier tipo de integración de software, pero que ha tenido su auge en los últimos años para HTTP e intercambio de mensajes entre microservicios.\nTipos de contrato Contrato explícito Cuando hablamos de un contrato explícito hacemos referencia al hecho de que el consumidor defina durante la ejecucción de sus test un archivo con la estructura del contrato que espera que se cumpla. Es entonces cuando dicho archivo se valida contra el que genera el proveedor, que lo crea de la misma forma, tras haber ejecutado sus propios test.\nLa clave de este tipo de contrato es que ambas partes hagan los test necesarios para asegurar que se cumple el contrato.\nDado que tanto proveedor como consumidor son de nuestro dominio, cuando queramos cambiar el contrato entre ambos, propagaremos el cambio cambiando el contrato en el proveedor y propagando el cambio en el consumidor para que ambos se equiparen con la nueva versión.\nContrato implícito El contrato implícito es muy similar al explícito, con la diferencia de que en este caso el proveedor no puede acoplarse al ciclo de test entre consumidor y proveedor. El caso más típico de como puede ocurrir esto es el caso de una API pública. Podemos consumirla, pero ese proveedor como es ajeno y probablemente de soporte para otros muchos servicios no podemos hacer que cumpla un contrato explícito.\nEn su lugar, el archivo que genera nuestro consumidor mediante la ejecucción de los test esperando que se cumpla un contrato determinado se valida contra la salida de un de doble de test que simula ser el proveedor real. En este caso es importante que configuremos algún tipo de alerta que nos avisa cuando el proveedor real cambia su salida para que nosotros la actualicemos en nuestro proveedor falso. Recalcar que en este caso el falso proveedor se encuentra integrado dentro del mismo artefacto donde está el consumidor. El punto negativo de este método es que cuando el proveedor cambie el mensaje que publica, hará que nuestro código falle hasta que nuestra batería de test se lancen y se vuelva a comprobar que se cumple el contrato, algo que no se estaría cumpliendo y por lo tanto tendríamos que actualizar el contrato que espera nuestro consumidor.\nVentajas de Consumer Driven Contract Testing ¿Qué ganamos con el uso de una herramienta como esta? Pues evitamos que tener que hacer test end to end muy costosos para verificar que la integración entre nuestros microservicios funciona correctamente. Nos ahorra mantener el entorno de ejecucción que levantamos para los test end to end. Recibimos feedback más rápido.\nComo único problema de este tipo de contract testing es que representa un entorno simulado, mientras que un test end to end representa un entorno más real, por lo que depende del caso según el tipo de integración que estemos intentando testear, puede que nos salga más a cuenta para ir más seguros a producción los test end to end, pero lo dicho, para una comunicación básica entre servicios basada en mensajes o vía API sale muy rentable utilizar consumer driven contract testing.\nHerramientas para su uso Pact es una herramienta de prueba de contratos impulsada por el consumidor que genera contratos explícitos mediante el uso de un doble de prueba que registra las solicitudes y las respuestas esperadas a un contrato que se denomina «pacto», el \u0026lsquo;contrato\u0026rsquo; del que hablabamos anteriormente.\nDurante la ejecucción de los test del consumidor se realiza una solicitud al proveedor simulado de Pact que registra en el archivo del contrato junto con su respuesta esperada. Con esto hemos garantizado que desde el lado del consumidor se sigue cumpliendo el contrato. Tras esto, una simulación del consumir que nos provee Pact vuelve a ejecutar una solicitud, pero esta vez contra el proveedor real y compara la respuesta actual con la esperada. Si la salida del consumidor simulado es igual a la real se cumple el contrato por parte del proveedor. Entonces, tras haberse cumplido el contrato por ambas partes nos hemos asegurado que nuestros dos servicios, proveedor y consumidor, se comunicarán correctamente en el entorno real, véase producción.\nConsumer Driven Contract Testing + Continuous Integration/Continuous Deployment La guinda en el pastel es que automaticemos el uso de Contract Testing haciendo que se integre en nuestro CI/CD, asegurándonos así que con cada build se ejecute y que nos permita deployar solo si el contrato se cumple. En el caso de que utilicemos Pact como nuestra herramienta para contract testing será necesario configurar Pact Broker, una aplicación para compartir contratos impulsados por el consumidor. Está optimizado para su uso con «pactos» (contratos creados por el framework Pact), pero puede utilizarse para cualquier tipo de contrato que pueda serializarse en JSON ¿Y qué ganamos usando esto? El Pact Broker permite desacoplar fácilmente el ciclo de lanzamiento de Consumer y Provider, unificando en un solo punto los resultado obtenidos y demás información interesante sobre el pacto, como pueda ser documentación entre otros detalles. Quizás como único inconveniente es que Pact Brocker al ser Open Source es self hosting, por lo que otra alternativa puede ser Pactflow, un fork de Pact Broker que ofrece una serie de ventajas sobre su padre que las que podemos destacar que está pensada para equipos ofreciendo una mejor interfaz de usuario, almacenamiento a cargo de la aplicación, una gran mejora en seguridad en lo que se refiera a inicio de sesión, y demás features que se pueden seguir destacando.\nPara que puedas realizar o ver un ejemplo práctico te recomiendo seguir el workshop creado por la propia gente de Pactflow en el que detallan como realizar Consumer Driven Contract Testing e integrarlo con CI/CD. Ver el siguiente enlace: Pactflow Workshop - CI/CD\n","permalink":"https://raulpadilladelgado.github.io/blog/p/consumer-driven-contract-testing/","summary":"\u003ch2 id=\"introducción\"\u003eIntroducción\u003c/h2\u003e\n\u003cp\u003e\u003cem\u003eConsumer driven contract testing\u003c/em\u003e es un tipo de prueba que nos garantiza que un proveedor cumple lo acordado con\nun consumidor. Por ejemplo, en una API usaríamos este tipo de pruebas para asegurar que el proveedor recibe una\npetición y devuelve la respuesta esperada.\u003c/p\u003e\n\u003cp\u003eCuando dos artefactos se comunican entre sí, típicamente lo suelen hacer mediante un mensaje, el cual tiene un\nformato determinado. Ese formato hace que el consumidor dependa del productor para poder funcionar correctamente, ya que\nsu código se ajusta a un determinado formato de mensaje. Ese «acuerdo» entre ambas partes es lo que se denomina\n«contrato». Es entonces gracias a \u003cem\u003econtract testing\u003c/em\u003e cuando podemos testear esa integración y asegurarnos que el mensaje\nque se envía de un artefacto a otro tiene el formato acordado para que la integración entre ambos funcione a la\nperfección.\u003c/p\u003e","title":"Consumer Driven Contract Testing"},{"content":"Contexto CQRS son las siglas de Command and Query Responsibility Segregation, un patrón que separa las operaciones de lectura y actualización para un almacén de datos. La flexibilidad creada por la migración a CQRS permite que un sistema evolucione mejor con el tiempo y evita que los comandos de actualización causen conflictos de fusión a nivel de dominio. Perfecto para aplicar en aplicaciones con gran carga de rendimiento.\nGracias a CQRS somo capaces de desacoplar la lógica de nuestro sistema por acciones, y en los siguientes puntos veremos como trata de realizarlo.\nCommand En CQRS un command no es un comando de CLI aunque la palabra nos lleve a ello\nEn CQRS, un Command representa la intención de realizar una operación en nuestro sistema que acabe modificando el estado de tal. Mediante un command enviamos información a nuestro sistema para que este sea alterado.\nLos commands son DTO (Data Transfer Object) encargados de pedir modificaciones en el sistema mediante operaciones conocidas como Post, Put, Path, Delete. Un command no devuelve nada, por lo que el body de la respuesta estará vacío.\nUn command es inmutable, y la razón es que si desde un controlador nos pasan un DTO (command) con una determinada información, y no tendría sentido que alteremos el objeto ya que sería modificar la tarea que nos han pedido.\nPodemos tener commands síncronos, para operaciones pocos costosos en tiempo de ejecucción y commands asíncronos para las muy costosas.\nEl command acaba en un CommandHandler que tratará los datos como pueda ser crear Value Objects para poder realizar validaciones y asegurar que el command cumple con los requisitos del dominio, y seguidamente el CommandHandler enviaría estos datos al caso de uso para que se encargue de ir al repositorio para modificar los registros pertinentes.\nQueries Una query en CQRS no es la tradicional query SQL a la que estamos acostumbrados.\nUna Query representa la intención de pedir unos datos a nuestro sistema sin que ello acabe alterando el estado de tal.\nLas queries son DTO encargados de pedir información al sistema y que por consiguiente hacen que una respuesta vuelva a nosotros mediante la operación conocida como Get.\nLa query acaba en un QueryHandler que tratará los datos como pueda ser crear Value Objects para poder realizar validaciones y asegurar que el command cumple con los requisitos del dominio, y seguidamente el QueryHandler enviaría estos datos al caso de uso (servicio de aplicación) para que se encarge de ir al repositorio para recuperar el vídeo, devolverlo y transformarlo en la respuesta esperada.\nCommands and Queries Bus Estos bus son las interfaces que actúan de intermediario entre los commands/queries y los Handler correspondiente. Saben a que Handler tienen que llevar según el command o query que esté llegando.\nSu misión principal es quitarle la responsabilidad al controlador de tener que saber a que handler tiene que enviar una command o query determinado.\nPara ver que ganamos mediante el uso de los buses vamos a ver la evolución de una arquitectura más tradicional a una que utiliza estos buses.\nEtapa 1) Post ➡ Controller: En este caso realizamos una petición http tipo post al controlador, y este será el encargado de realizar toda la lógica. Es el caso tradicional más extremo pero iremos iterando para ver las diferencias. En este caso si quisiéramos añadir otro tipo de punto de entrada a nuestra aplicación distinto al controller (http), tendríamos que copiar la lógica de dicho controlador a el nuevo punto de entrada (ejemplo, un comando de terminal).\nEtapa 2) Post ➡ Controller ➡ Caso de uso: En este caso realizamos una petición http tipo post al controlador, y esté conoce a que caso de uso (un servicio de nuestra aplicación) debe dirigirse para que sea ejecutada la lógica. En este caso si quisiéramos añadir otro tipo de punto de entrada a nuestra aplicación distinto al controller (http), tendríamos que apuntar al mismo caso de uso que utilizaba el controller desde el nuevo punto de entrada (ejemplo, un comando de terminal). Gracias a que tenemos la lógica separada del controlador logramos reutilización de código.\nEtapa final) Post ➡ Controller ➡ Instancia command (DTO) ➡ CommandBus (interface) ➡ CommandHandler ➡ Caso de uso: En este caso realizamos una petición http tipo post al controlador, el cual instancia un command (DTO) que será enviado al CommandBus, que es una interface de dominio que será posteriormente implementada. El CommandBus sabe según que tipo de command está recibiendo a que CommandHandler debe destinarse. En este caso si quisieramos añadir otro tipo de punto de entrada a nuestra aplicación distinto del controller (http), tendríamos instanciar del mismo modo el command deseado, y enviarlo al CommandBus para que este se destine al CommandHandler que toque. Además de ganar en reutilización de código como hacíamos en el paso b, también estamos desacoplando al controlador de conocer a que caso de uso debe dirigirse, simplemente pedirá commands al CommandQuery, hacemos que los controladores que son algo que naturalmente son de infraestructura no conozcan como funciona nuestro dominio. Entonces si el día de mañana queremos cambiar que se debe hacer con un tipo de command determinado, simplemente tendremos que cambiar la lógica en el CommandHandler y así los N puntos de entrada a la aplicación (controladores, comandos de terminal, etc) no necesitarán cambiar nada de su comportamiento. Previamente hemos comentado que el CommandBus es una interfaz que debe ser implementada, y esto nos da la posibilidad de crear una implementación asíncrona del CommandBus para que las peticiones no tengan que ser resultas en el mismo momento en el que se realizan, si no que se irán procesando y se pasará la respuesta cuando esté disponible.\n💡 En esta comparación de diseño hemos hablado solo de los Command Bus, pero se aplica exactamente el mismo funcionamiento cuando se trate de una Query y su determinado Query Bus\nCQRS + Event Sourcing CQRS complementa la idea tradicional del Event sourcing de desacoplar nuestro código lanzando eventos y que otro módulos de nuestra aplicación se suscriban a tales eventos para interactuar con ellos. Por lo que vamos a ver como se acoplan estos dos patrones de diseños y nos brindan los mejor de ambos con el siguiente esquema:\nBefore (Only CQRS) After (CQRS + Event Sourcing) Mitos y otras dudas Cuando estuve aprendiendo que era el CQRS me hice la pregunta de que si iba a necesitar dos bases de datos, una para el modelo de escritura y otro para el de lectura. En parte los contenidos que leia me llevaban a pensar eso, pero si lo pensamos bien, tendría sentido usar dos bases de datos que se sincronicen entre sí solo si nuestra aplicación lo requiere, es decir, que vayamos a tener que manejar un gran volumen de datos, y/o que sepamos que la consulta y modificación puedan interferir entre sí, pero lo dicho, no es la regla general.\nPara más dudas sobre CQRS dejo un enlace que me ha sido de gran ayuda: https://event-driven.io/en/cqrs_facts_and_myths_explained/\nEjemplo práctico Navegando por Github me encontré un buen ejemplo de la implementación de CQRS + Event Sourcing utilizando Java, ver el siguiente enlace:\nJava-CQRS-Introducción\n","permalink":"https://raulpadilladelgado.github.io/blog/p/command-query-responsibility-segregation/","summary":"\u003ch1 id=\"contexto\"\u003eContexto\u003c/h1\u003e\n\u003cp\u003eCQRS son las siglas de Command and Query Responsibility Segregation, un patrón que separa las operaciones de lectura y actualización para un almacén de datos. La flexibilidad creada por la migración a CQRS permite que un sistema evolucione mejor con el tiempo y evita que los comandos de actualización causen conflictos de fusión a nivel de dominio. Perfecto para aplicar en aplicaciones con gran carga de rendimiento.\u003c/p\u003e\n\u003cp\u003eGracias a CQRS somo capaces de desacoplar la lógica de nuestro sistema por acciones, y en los siguientes puntos veremos como trata de realizarlo.\u003c/p\u003e","title":"Command Query Responsibility Segregation"},{"content":"Introducción Este post tiene como objetivo mostrar una serie de trucos o tips sobre Java que no son más que nuevan funcionalidades que han ido saliendo con el paso de los años y que hoy recojo aquí con la intención de mostrar las más útiles y ejemplos de uso. Pertenece a una serie de post que siguen el mismo objetivo, puedes buscar en blog los demás post.\nTry with resources Desde Java 7 existe la fórmula try-with-resources que permite vincular el cerrado de recursos a la conclusión del try, de modo que no se nos olvide hacerlo manualmente.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Con finally String line = null; BufferedReader br = new BufferedReader(new FileReader(\u0026#34;myfile\u0026#34;)); try { line = br.readLine(); } catch (Exception e) { e.printStackTrace(); } finally { if (br != null) br.close(); } // Con try-with-resources String line = null; try (BufferedReader br = new BufferedReader(new FileReader(\u0026#34;myfile\u0026#34;))) { line = br.readLine(); } catch (Exception e) { e.printStackTrace(); } Como se puede observar, definimos los recursos que deben ser cerrados automáticamente después del try y entre paréntesis. Podemos incluir varios recursos separándolos por punto y coma. Al escribirse de esta forma se llamará al método close del BufferedReader al acabar la ejecución del bloque, se produzcan errores o no.\nTodos los recursos que se utilicen dentro de un try-with-resources deben implementar la interfaz AutoCloseable, la cual tiene un único método close que define cómo se debe cerrar el recurso.\nFechas LocalDateTime, LocalTime, LocalDate, TimePoint 1 2 3 4 5 6 7 8 9 10 11 LocalDateTime timepoint = LocalDateTime.now(); // Fecha y hora actual LocalDate date = LocalDate.of(2020, Month.JULY, 27); // Obtenemos la fecha indicada LocalTime.of(17, 30); // Obtenemos la hora indicada LocalTime.parse(\u0026#34;17:30:00\u0026#34;); // Otra forma para la hora Month month = timepoint.getMonth(); //Obtener el mes actual int day = timepoint.getDayOfMonth(); //Obtener el número de día de actual // TimePoint es inmutable, así que cambiar el valor retorna un nuevo objeto y podemos realizar un desarrollo más funcional LocalDateTime happyTwenties = timepoint.withYear(1920) .withMonth(Month.January) .withDayOfMonth(1) .plusWeeks(3); Formateado de fechas 1 2 3 Date date = new Date(); // Fecha actual SimpleDateFormat df = new SimpleDateFormat(\u0026#34;dd-MM-yyyy\u0026#34;); String date = df.format(date); //Formato final de la fecha en String Genéricos El término “Generic” viene a ser como un tipo parametrizado, es un tipo de dato especial del lenguaje que permite centrarnos en el algoritmo sin importar el tipo de dato específico que finalmente se utilice en él. Muchos algoritmos son los mismos, independientemente del tipo de dato que maneje. Por ejemplo, un algoritmo de ordenación.\nSe llaman parametrizados porque el tipo de dato con el que opera la funcionalidad se pasa como parámetro. Pueden usarse en clases, interfaces y métodos, denominándose clases, interfaces o métodos genéricos respectivamente. En Java, la declaración de un tipo genérico se hace entre símbolos \u0026lt;\u0026gt;, pudiendo definir uno o más parámetros, por ejemplo: , \u0026lt;K, V\u0026gt;. Existe una convención a la hora de nombrarlos:\nE – Element (usado bastante por Java Collections Framework). K – Key (usado en mapas). N – Number (para números). T – Type (representa un tipo, es decir, una clase). V – Value (representa el valor, también se usa en mapas). S, U, V etc. – usado para representar otros tipos Ejemplo de clase genérica\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // GenericClass.java file public class GenericClass\u0026lt;T, K\u0026gt; { private T g1; private K g2; public GenericClass(T g1, K g2) { this.g1 = g1; this.g2 = g2; } public T getGenericOne() { return g1; } public K getGenericTwo() { return g2; } } // Main.java file public class Main{ public static void main(String[] args) throws Exception { GenericClass\u0026lt;Integer, String\u0026gt; clazz = new GenericClass\u0026lt;\u0026gt;(1, \u0026#34;generic\u0026#34;); Integer param1 = clazz.getGenericOne(); String param2 = clazz.getGenericTwo(); System.out.println(String.format(\u0026#34;Param1 %d - Param2 %s\u0026#34;, param1, param2)); } } Ejemplo de método genérico\n1 2 3 4 5 6 7 8 9 10 11 12 13 // WhiteBoard.java file public class WhiteBoard { public \u0026lt;T\u0026gt; void draw(T figure ) { ... } } // Main.java file public static void main(String[] args) throws Exception { WhiteBoard board = new WhiteBoard(); Figure circle = new Circle(1.5); board.draw(circle); } Interfaces funcionales En Java, se considera interfaz funcional a toda interfaz que contenga un único método abstracto. Es decir, interfaces que tienen métodos estáticos o por defecto (default) seguirán siendo funcionales si solo tienen un único método abstracto.\nEjemplo:\n1 2 3 4 5 6 7 8 9 10 @FunctionalInterface public interface SalaryToPrettyStringMapper { default List\u0026lt;String\u0026gt; map(List\u0026lt;Salary\u0026gt; list) { return list.stream() .map(this::map) .collect(Collectors.toList()); } String map(Salary salary); } La anotación @FunctionalInterface denota que es una interfaz funcional, pero es opcional y, aunque no estuviese, la interfaz seguiría siendo funcional. Sería buena práctica mantenerla para recordar que se trata de una interfaz funcional (no de una clase abstracta), y que en caso de añadir más métodos nos lanzaría un error de compilación.\nLambdas Las lambdas fueron introducidas a partir de Java 8. No son más que funciones anónimas que nos permiten programar en Java con un estilo más funcional y, en ocasiones, declarativo.\nLa sintaxis de una lambda es la siguiente:\n1 ( tipo1 param1, tipoN paramN) -\u0026gt; { cuerpo de la lambda } El operador flecha (-\u0026gt;), es característico de las lambda y separa los parámetros del cuerpo de la función. No es necesario incluir el tipo ya que este puede ser inferido. El paréntesis de los parámetros puede omitirse cuando sólo existe un parámetro y no incluimos el tipo. Si no hay parámetros los paréntesis son necesarios.\n1 2 3 (param1, param2) -\u0026gt; { cuerpo } param1 -\u0026gt; { cuerpo } () -\u0026gt; { cuerpo } En el caso del cuerpo, si solo tenemos una sentencia, podremos omitir las llaves y el return, por ejemplo:\n1 numero -\u0026gt; String.valueOf(numero) Si tenemos más de una, las llaves serán necesarias:\n1 2 3 4 numero -\u0026gt; { String cadena = String.valueOf(numero); return cadena; } ¿Donde usar las lambdas? Las lambdas se pueden usar en cualquier parte que acepte una interfaz funcional. La lambda tendrá que corresponder con la firma del método abstracto de la interfaz funcional.\nSe pueden asignar a variables tipadas:\n1 2 Predicate\u0026lt;Integer\u0026gt; isOdd = n -\u0026gt; n % 2 != 0; isOdd.test(2); // false Pueden ser parte del return de un método:\n1 2 3 private Predicate\u0026lt;Integer\u0026gt; isOddPredicate() { return n -\u0026gt; n % 2 != 0; } En llamadas a métodos:\n1 2 3 4 5 6 IntStream.range(0, 2) .mapToObj(entero -\u0026gt; String.format(\u0026#34;entero = %s\u0026#34;, entero)) .forEach(cadena -\u0026gt; System.out.println(cadena)); // Salida: // entero = 0 // entero = 1 Referencias a métodos Cuando un método coincida con la firma de una interfaz funcional, podremos usar una referencia al método en vez de la sintaxis habitual de las lambdas\n1 2 3 IntStream.range(0, 2) .mapToObj(entero -\u0026gt; String.format(\u0026#34;entero = %s\u0026#34;, entero)) .forEach(System.out::println); // \u0026lt;- Referencia a método Para usar referencias a métodos, ponemos (::) justo antes del método, en vez de un punto, e ignoramos los paréntesis. Así pues, estas podrían ser referencias válidas a métodos:\n1 2 3 4 System.out::println this::miMetodo super::metodoDeSuper unObjeto::suMetodo Data processing Streams Desde Java 8 podemos hacer uso de estas herramientas para simplificar la forma en la que interactuamos con las colecciones, evitando que tengamos que realizar bucles complejos. Nos permiten hacer operaciones paralelizables, concatenando instrucciones con un estilo declarativo.\nPara utilizarlas sera necesario llamar a stream() o parallelStream(), en función de si queremos paralelizar las operaciones o no.\nUn stream simplemente recibe los datos de una colección y genera un resultado tras el procesado de las operaciones intermedias. Estas operaciones intermedias devuelven un stream, por lo que será necesario ejecutar una operación terminal para que las intermedias se ejecuten y poder obtener un resultado.\nUn ejemplo para verlo más claro:\n1 2 3 4 5 List\u0026lt;Other\u0026gt; l2 = l1.stream() .filter(elem -\u0026gt; elem.getAge() \u0026lt; 65) .sorted() // Ordena según la implementación de Comparable .map(elem -\u0026gt; new Other(elem.getName,() elem.getAge())) .collect(toList()); En el ejemplo anterior hemos visto algunas operaciones intermedias como son filter(), entre otras, y a demás el bloque de instrucciones termina con un collect(), que se trata de una operación terminal que nos permite pasarle un parámetro de tipo Collector, y que en este caso con el toList() conseguimos que se nos devuelva una lista como teníamos al principio.\nLas operaciones intermedias se pueden clasificar en:\nFIltrado Búsqueda Mapeado Matching Reducción Iteración Los streams pueden ser utilizados para más propósitos, como pueda ser un array:\n1 2 int[] array = {1, 2, 3, 4, 5}; int sum = Arrays.stream(array).sum(); O para convertir un fichero en un stream de líneas:\n1 2 3 4 long numberOfLines = Files.lines( Paths.get(\u0026#34;yourFile.txt\u0026#34;), Charset.defaultCharset() ).count(); También se pueden crear Stream a partir de valores usando Stream.of\nOptional A partir de Java 8 tenemos implementado en Java el patrón option. Se basa en indicar que se puede devolver o no el valor esperado obligando a que tengamos contemplados ambos escenarios.\nLa clase Optional en Java nos dispone de constructor, en su lugar usaremos sus métodos de factoría estáticos para crear los optional. Veamos:\n1 2 3 public static \u0026lt;T\u0026gt; Optional\u0026lt;T\u0026gt; empty() //Devuelve objeto opcional vacío public static \u0026lt;T\u0026gt; Optional\u0026lt;T\u0026gt; ofNullable(T value) //Devuelve objeto con valor o si es null un objeto vacío public static \u0026lt;T\u0026gt; Optional\u0026lt;T\u0026gt; of(T value) //Devuelve objeto con valor o si es null retorna NullPointerException La clase Optional nos proporciona una serie de métodos:\n1 2 3 4 5 6 7 8 public boolean isPresent() //Indica si el objeto tiene o no valor public T get() //Retorna el valor almacenado. Si no hay valor retorna excepción public Optional\u0026lt;T\u0026gt; filter(Function f) //Comportamiento como con los streams public \u0026lt;U\u0026gt; Optional\u0026lt;U\u0026gt; map(Function f) //Comportamiento como con los streams public \u0026lt;U\u0026gt; Optional\u0026lt;U\u0026gt; flatMap(Function f) //Comportamiento como con los streams public T orElse(T other) //Nos retorna el valor original. Si es nulo, retorna el valor que pasamos por parámetro public T orElseGet(Function f) //Igual que el anterior pero el parámetro es una función public \u0026lt;X extends Throwable\u0026gt; T orElseThrow(Function f) //Retorna el valor original. Si es nulo, retorna la excepción que devuelva la función pasada como parámetro ","permalink":"https://raulpadilladelgado.github.io/blog/p/java-moderno-cap%C3%ADtulo-1/","summary":"\u003ch2 id=\"introducción\"\u003eIntroducción\u003c/h2\u003e\n\u003cp\u003eEste post tiene como objetivo mostrar una serie de trucos o tips sobre Java que no son más que nuevan funcionalidades\nque han ido saliendo con el paso de los años y que hoy recojo aquí con la intención de mostrar las más útiles y ejemplos\nde uso. Pertenece a una serie de post que siguen el mismo objetivo, puedes buscar en blog los demás post.\u003c/p\u003e\n\u003ch2 id=\"try-with-resources\"\u003eTry with resources\u003c/h2\u003e\n\u003cp\u003eDesde Java 7 existe la fórmula try-with-resources que permite vincular el cerrado de recursos a la conclusión del try, de modo que no se nos olvide hacerlo manualmente.\u003c/p\u003e","title":"Java Moderno (Capítulo 1)"},{"content":"Introducción En nuestras aplicaciones Python solemos hacer uso de librerías, y las diferentes versiones de estas pueden llegar a ser un quebradero de cabeza cuando son usadas desde varias aplicaciones. Además de las librerías, la propia instalación de Python tiene un sistema de versionado, por lo que nos ocurre el mismo problema. Una aplicación A no funciona con Python 3.8 pero la aplicación B lo necesita.\nLa solución a este problema es usar un entorno virtual, que es un árbol de directorios autónomo que contiene una instalación de Python con una versión concreta, además de una serie de paquetes adicionales que queramos instalar.\nDiferentes aplicaciones pueden usar diferentes entornos virtuales. Si una aplicación A necesita una versión 1.0 de un paquete y otra aplicación B la versión 2.0 no tendrán conflictos ya que dentro de cada aplicación habrá un entorno virtual (carpeta) que contenga sus propias instalaciones. Por lo que si dentro de una aplicación B se quiere usar la versión más nueva del paquete 3.0, la aplicación A seguirá usando la versión 1.0 y no se verá afectada. No es estrictamente necesario que el entorno virtual (carpeta) esté dentro del proyecto en cuestión, pero si suele ser una recomendación a la hora de buscar organización y no tener los entornos virtuales desperdigados por el sistema de archivos de nuestra máquina.\nAdemás de solucionar problemas entre las versiones de distintos proyectos, usar entornos virtuales nos permite poder aislar nuestro sistema (PC) de tener que instalar globalmente todas las dependencias que tiene un proyecto, por lo que cuando borremos el proyecto no tendremos todas esas dependencias instaladas en nuestra máquina.\nEntornos virtuales desde terminal Crear entornos virtuales El módulo de python que permite crear y manejar entornos virtuales se llama venv. Para crear un entorno virtual ejecuta dentro de un directorio:\npython3 -m venv tutorial-env python3: por defecto se instalara dentro del entorno virtual la misma versión de python que tengamos instalada en el sistema -m: Indicamos que vamos a introducir el nombre de un módulo de python venv: Es el módulo de python que nos permite crear y manejar entornos virtuales tutorial-env: Es el nombre que le queramos dar al entorno virtual Una vez creado el entorno virtual procederemos a activarlo, que hará que nuestra terminal tenga el contexto de ese entorno virtual y accederá a los paquetes instalados dentro en lugar de usar la de nuestro sistema.\nEn windows ejecuta:\ntutorial-env\\Scripts\\activate.bat En Unix (Linux) o MacOS ejecuta:\nsource tutorial-env/bin/activate 💡 Si usas otra terminal distinta al bash tienes implementaciones distintas del script de activación como por ejemplo para csh (activate.csh) o fish (activate.fish)\nActivar el entorno virtual hará que cambie el prompt de tu terminal para que muetre que entorno virtual se está usando.\nraul@DESKTOP:~/env-example$ source tutorial-env/bin/activate (tutorial-env) raul@DESKTOP:~/env-example$ python3 Python 3.8.10 (default, Sep 28 2021, 16:10:42) [GCC 9.3.0] on linux Type \u0026#34;help\u0026#34;, \u0026#34;copyright\u0026#34;, \u0026#34;credits\u0026#34; or \u0026#34;license\u0026#34; for more information. \u0026gt;\u0026gt;\u0026gt; Instalar paquetes dentro del entorno virtual Dentro de nuestro entorno virtual también podemos instalar, actualizar y eliminar paquetes usando el ejecutable pip.\n💡 Pip buscará los paquetes en la página https://pypi.org\nPara instalar la última versión de un paquete ejecutaremos:\n(tutorial-env) raul@DESKTOP:~/env-example$ python3 -m pip install novas (tutorial-env) ⇒ nos muestra que el entorno virtual está activado. Para realizar la instalación de un paquete python dentro del entorno virtual es necesario que lo tengamos activado primero. python3 -m ⇒ Indicamos que vamos a introducir el nombre de un módulo de python, en este caso pip. install ⇒ Para instalar el paquete novas ⇒ nombre del paquete Y si quisieras instalar una versión específica de un paquete lo podemos realizar concatenando los signos == y la versión del paquete:\n(tutorial-env) raul@DESKTOP:~/env-example$ python3 -m pip install requests==2.6.0 Para actualizar el paquete ejecutaríamos un install añadiendo el parámetro \u0026ndash;upgrade al comando:\n(tutorial-env) raul@DESKTOP:~/env-example$ python3 -m pip install --upgrade requests Para instalar uno o varios paquetes pon sus nombres después de pip uninstall\nPara mostrar información sobre un paquete en particular pon su nombre después de pip show\nPara listar todos los paquetes instalados en el entorno virtual podemos ejecutar pip list\nPara listar todos los paquetes instalados y además guardar la salida del comando en un fichero de texto que tenga un formato preparado para que instalar todas las versiones a la vez desde pip ejecuta:\n(tutorial-env) raul@DESKTOP:~/env-example$ python3 -m pip freeze \u0026gt; requirements.txt (tutorial-env) raul@DESKTOP:~/env-example$ cat requirements.txt novas==3.1.1.3 numpy==1.9.2 requests==2.7.0 freeze ⇒ Similar al list pero expulsa la salida en un formato compatible con la instalación masiva de todos los paquetes que contiene requirements.txt ⇒ Redirigir la salida al archivo especificado. El nombre no tiene que ser exactamente el del ejemplo, pero suele ser un estándar usar ese nombre. Ejemplo de diferencias entre list y freeze\nLIST novas (3.1.1.3) numpy (1.9.2) requests (2.7.0) FREEZE novas==3.1.1.3 numpy==1.9.2 requests==2.7.0 El fichero requirements.txt debe ser guardado mediante el control de versiones para que cualquier usuario que se baje el proyecto pueda instalar todas las dependencias necesarias utilizando:\n(tutorial-env) raul@DESKTOP:~/env-example$ python3 -m pip install -r requirements.txt -r ⇒ Indica que las dependencias están especificadas en un archivo que vamos a pasar Entornos virtuales desde IntelliJ IDEA Ultimate o PyCharm Previamente hemos visto como manejar los entornos virtuales desde terminal, pero en algún momento tendríamos que abrir nuestro proyecto desde un IDE, y en este caso explicaré como manejar los entornos virtuales dentro de los IDEs de Jetbrains.\nRequisitos:\nSi vas a utilizar IntelliJ (Ultimate) en lugar de PyCharm es necesario que tengas instalado el plugin de Python. Lo puedes instalar desde los ajustes del IDE (Ctrl + Alt + S) Se recomienda instalar el plugin “Requirements” para poder manejar de una forma más cómoda y más asistida los requirimientos dentro del proyecto Crear un entorno virtual Para crear un entorno virtual desde el IDE abriremos los ajustes de la estructura del proyecto (Ctrl + Shift + Alt + S) y añadiremos un nuevo SDK de Python\nBase interpreter: En caso de que no tengamos Python instalado en nuestra máquina el IDE nos ofrecerá descargar una versión. Si ya lo tenemos instalado lo autodetectará, pero también podemos indicar una ruta donde esté Python instalado.\nInherit global site-packages: Nos incluirá dentro del entorno virtual todos los paquetes que tengamos instalados. Se recomienda deshabilitar si globalmente tenemos muchas dependencias que no sean de necesidad para el proyecto. Corresponde con el parámetro -system-site-packages de virtualenv.\nMake available to all projects: El IDE guardará el entorno virtual para que pueda ser usado en otro proyectos. Actívalo si sabes que las dependencias de tu proyecto serán las mismas en algún otro proyecto.\nUsar un entorno virtual existente Para usar un entorno virtual existente desde el IDE abriremos los ajustes de la estructura del proyecto (Ctrl + Shift + Alt + S) y añadiremos un SDK de Python\nInterpreter: Indicaremos la ruta donde está instalado Python dentro del entorno virtual\nMake available to all projects: El IDE guardará el entorno virtual para que pueda ser usado en otro proyectos. Actívalo si sabes que las dependencias de tu proyecto serán las mismas en algún otro proyecto.\nIncluir las dependencias del archivo requirements Con el plugin “Requirements” instalado en pasos anteriores podremos instalar los paquetes necesarios dentro del archivo requirements.txt\nPara sincronizar las dependencias que están instaladas en el entorno virtual y lo que tenemos definido en el archivo requirements.txt en la barra superior del IDE pulsamos sobre “Tools” y elegimos la opción “Sync Python Requirements…”\nEntornos virtuales en proyectos versionados Un entorno virtual no se versiona junto con el proyecto, recordemos que esa carpeta tiene multitud de instalaciones de paquetes de Python y que estaríamos añadiendo una carpeta de mucho peso en el sistema de versionado. El proceso que debemos seguir para recrear el entorno virtual cuando nos bajamos un proyecto de Python es:\nCrear un entorno virtual nuevo Instalar las dependencias que están especificadas en el archivo requierements.txt del proyecto ","permalink":"https://raulpadilladelgado.github.io/blog/p/entornos-virtuales-en-python/","summary":"\u003ch1 id=\"introducción\"\u003eIntroducción\u003c/h1\u003e\n\u003cp\u003eEn nuestras aplicaciones Python solemos hacer uso de librerías, y las diferentes versiones de estas pueden llegar a ser un quebradero de cabeza cuando son usadas desde varias aplicaciones. Además de las librerías, la propia instalación de Python tiene un sistema de versionado, por lo que nos ocurre el mismo problema. Una aplicación A no funciona con Python 3.8 pero la aplicación B lo necesita.\u003c/p\u003e\n\u003cp\u003eLa solución a este problema es usar un entorno virtual, que es un árbol de directorios autónomo que contiene una instalación de Python con una versión concreta, además de una serie de paquetes adicionales que queramos instalar.\u003c/p\u003e","title":"Entornos virtuales en Python"},{"content":"Introducción SSH o Secure Shell Protocol, es un protocolo de comunicación así como pueda serlo los muy conocidos HTTP, HTTPS o FTP.\nEn el caso de SSH nos permite la comunicación entre dispositivos dentro de la red, así como controlar o modificar ordenadores remotos.\n¿Que ofrece de nuevo entonces frente a otros protocolos de comunicación? Pues básicamente es un protocolo orientado a mejorar la seguridad de la comunicaciones encriptando los datos de modo que intrusos no puedan ver contenido protegido bajo el protocolo.\nHTTPS puede sonar parecido a SSH, ambos son seguros y permiten la comunicación, pero la diferencia está en que HTTPS está pensado para la web y SSH para la shell (línea de comandos que usa desde un sistema operativo).\nSSH pretende encriptar las comunicaciones entre cliente (dispositivo que se conecta al host) y host (servidor remoto al que accede el cliente)\nLas conexiones SSH están muy presentes en nuestro día a día. En el caso de github utilizamos SSH para clonar, hacer push o hacer pull a sus servidores.\nComo funciona la encriptación de SSH Existen tres tipos principales de técnicas de encriptación usadas por SSH:\nEncriptación simétrica Encriptación asimétrica Hash Encriptación simétrica El ordenador A quiere mandarle al ordenador B un mensaje del estilo \u0026ldquo;hola, que tal?\u0026rdquo;, pero el ordenador A no quiere que nadie se entere de lo que está diciendo. Entonces utilizará una \u0026ldquo;llave\u0026rdquo; para alterar su mensaje y el \u0026ldquo;hola, que tal?\u0026rdquo; pasará a ser por ejemplo \u0026ldquo;EK25+5\u0026rdquo;. El ordenador B también tiene esa \u0026ldquo;llave\u0026rdquo;, por lo que es capaz de descifrar el mensaje alterando el mensaje cifrado para recuperar el original.\nAsí funciona el cifrado simétrico, quien tenga la llave es capaz de descifrar lo que se está transfiriendo.\nLa llave que conocen ambas partes de la comunicación no puede viajar a través de la red, ya que un atacante podría estar espiando y conseguir una copia de la llave, por lo que también podría descifrar la información que enviemos bajo el uso de dicha llave. Para evitar que la llave viaje de forma insegura se utiliza un algoritmo de intercambio de claves. Sería algo así como definir un algoritmo para que la llave pueda viajar por la red de forma segura ya que estará cifrada y la forma de descrifrarlo solo la conocen ambas partes interesadas en la comunicación. Esto será el siguiente punto que tratemos, la encriptación asimétrica.\nEncriptación asimétrica Para la encriptación asimétrica se utilizan dos llaves en ambas partes de la comunicación. Cada parte tiene una llave pública y una llave privada. Las claves públicas pueden ser compartidas con todo el mundo, no supone un riesgo para nuestra seguridad. Sin embargo, las claves privadas deben permanecer en secreto y no compartirlas con nadie. La clave pública está relacionada con una clave privada en término de funcionaliadad, aunque compartir la clave pública es seguro porque a partir de esta no es posible calcular la clave privada. Esto quiere decir que un mensaje que fue encriptado usando una clave pública, solo podrá ser desencriptado utilizando su respestiva clave privada.\nEl ordenador A y el ordenador B intercambian sus claves públicas. El ordenador A encripta un mensaje utilizando la clave pública del ordenador B, envía el mensaje y finalmente el ordenador B utiliza su propia clave privada para desencriptar el mensaje\ndiffie hellman key exchange ‎El algoritmo de intercambio de claves Diffie Hellman (DH) es un método para intercambiar de forma segura claves criptográficas a través de un canal de comunicaciones público. Las claves no se intercambian realmente, se derivan conjuntamente. Lleva el nombre de sus inventores Whitfield Diffie y Martin Hellman.‎ Mejora el intercambio de claves que hemos visto anteriormente en la encriptación simétrica y asimétrica utilizando cálculo matemáticos avanzados para que sea prácticamente imposible que un intruso en la comunicación sea capaz de descifrar la clave que permite acceder a los recursos.\nEjemplo\n‎Si Alice y Bob desean comunicarse entre sí, primero acuerdan entre ellos un gran número primo n y un generador (o base) g (donde 0 \u0026lt; g \u0026lt; n).‎\n‎Alice elige un entero secreto a (su clave privada) y luego calcula g^a mod n (que es su clave pública). Bob elige su clave privada b y calcula su clave pública de la misma manera.‎\n‎Bob conoce b y g^a, por lo que puede calcular (g^a)^b mod n = g^ab mod n. Por lo tanto, tanto Alice como Bob conocen un secreto compartido g^ab mod n. Eva, una oyente maliciosa en la comunicación conoce n, g, la clave pública de Alice (g^a mod n) y la clave pública de Bob (g^b mod n). Ella es incapaz de calcular el secreto compartido a partir de estos valores, sería necesario conocer la clave privada.\nHash Con esto ganamos que ningún intermediario malicioso se meta en la comunicación y suplante la identidad del host y del cliente aportando su propia clave púlica para así poder descifrar los mensajes.\nEs una técnica de encriptación en la que para la mismta entrada (texto) siempre se produce la misma salida (texto encriptado). Desde el texto encriptado utilizando hash no seríamos capaces de llegar al texto como era en el inicio. Entonces solo la persona que sabe el secreto volverá a introducir la misma entrada y esta vez el servidor comprobara que se registra el mismo hash que guardó al principio, en caso de ser así, la persona estaría autorizada.\nCon esta ténica se requiere una entrada segura, por ejemplo, si hablamos de contraseñas, una del estilo \u0026ldquo;1234\u0026rdquo; seguramente sea de las primera que prueban los atacantes, sin embargo, recurrir a algo más elaborado como \u0026ldquo;d56fg4583+*dfh347vfh\u0026rdquo; seguro que no será fácil de adivinar.\nEjemplo\nEl comando md5sum imprime una suma de comprobación de 32 caracteres (128 bits) del archivo dado, utilizando el algoritmo MD5.\nVamos a abrir una terminal UNIX y usaremos el comando md5sum para crear y comparar hashes entre ficheros:\nDos ficheros iguales, mismo hash\n$ cat file1.txt\nhello world\n$ cat file2.txt\nhello world\n$ md5sum file1.txt\n6f5902ac237024bdd0c176cb93063dc4 file1.txt\n$ md5sum file2.txt\n6f5902ac237024bdd0c176cb93063dc4 file2.txt\nDos ficheros distintos, distinto hash:\n$ cat file1.txt\nhello world\n$ cat file2.txt\nbye bye\n$ md5sum file1.txt\n6f5902ac237024bdd0c176cb93063dc4 file1.txt\n$ md5sum file2.txt\nb052b28f8360616ca92f434f497585ff file2.txt\nComprobación masiva de hashes:\n$ cat file1.txt\nhello world\n$ cat file2.txt\nbye bye\n$ md5sum file1.txt file2.txt \u0026gt; hashes\n$ md5sum \u0026ndash;check hashes\nfile1.txt: OK\nfile2.txt: OK\n$ echo \u0026ldquo;!\u0026rdquo; \u0026raquo; file2.txt\n$ md5sum \u0026ndash;check hashes\nfile1.txt: OK\nfile2.txt: FAILED\nmd5sum: WARNING: 1 computed checksum did NOT match\nComo configurar claves publicas en un servidor Ubuntu para conectar mediante SSH Usando un proveedor de servidores como pueda ser Digital Ocean, vamos a crear un servidor ubuntu para accederlo vía SSH.\nTras haberlo creado vamos a escribir el siguiente comando en nuestro terminal para conectarnos al servidor que se encuentra en internet:\nssh {user}@{host}\nSiendo user el usuario (cuenta) por defecto que nos ha creado el servidor para acceder y host el servidor al que queremos acceder, en ese campo podremos escribir una dirección IP o un nombre de dominio.\nVeremos que estamos conectado desde la shell al servidor, por lo que podemos acceder a su sistema de archivos, ejecutar comandos, etc.\nVamos a utlizar RSA, que nos permitirá garantizar la identificación de los usuarios sin necesidad de utlizar una contraseña.\nDesde la consola de nuestro ordenador local vamos a generar un par de claves (pública y privada) para utilizarlas posteriormente con el servidor.\nssh-keygen -C \u0026ldquo;test@gmail.com\u0026rdquo;\n-C Comment Provides a new comment Tras esto nos pedirá donde queremos las claves. Por defecto lo hará en la ruta /Users/{your_user}/.ssh/id_rsa si solo presionamos enter, pero podemos especificar nosotros la ruta y el nombre de la clave privada también.\nNos iremos al servidor y nos conectaremos vía ssh. Nuestro proveedor de servidor no debería tener configurado por defecto el acceso mediante SSH solo para determinados usuarios, por lo que en teoría podríamos acceder en este momento con cualquier usuario.\nssh {user}@{host}\nY vamos a crear el folder .ssh en la ruta principal del usuario:\nmkdir .ssh\nDentro de la ruta .ssh crearemos un archivo que se nombre exactamente authorized_keys, en donde pegaremos el contenido generado de la clave pública generada anteriormente.\nTras haber realizado lo anterior cuando queramos conectarnos al servidor vía ssh veremos que esta vez lo ha intentado mediante la public key pero ha fallado porque no podrá escoger correctamente la key, la que hemos copiado dentro del servidor\nPara añadir la clave que nos interesa al comando ssh, tendremos que ejecutar lo siguiente :\nssh-add ~/.ssh/{your_private_key}\nadds private key identities to the authentication agent\nY ahora sí que si volvemos a conectarnos vía ssh podremos haber accedido al servidor sin necesidad de introducir una contraseña, ya que este conocía nuestra clave pública y nos ha mando un mensaje de verificación de identidad que hemos sabido descrifrar ya que fue cifrado con nuestra clave pública que solo se puede descifrar con nuestra clave privada.\nPara eliminar las claves que tiene el authentication agent:\nssh-add -D\nPara listar las claves que tiene el authentication agent:\nssh-add -l\nComo configurar SSH en Github Lo primero será abrir la terminal y generar la clave pública que añadiremos posteriomente en Github\nGithub nos recomienda que la clave que generemos sea usando el algoritmo Ed25519\nssh-keygen -t ed25519 -C \u0026ldquo;your_email@example.com\u0026rdquo;\nEn el caso de que el sistema que estés usando no permita el algoritmo Ed25519, puedes recurrir al método más tradicional:\nssh-keygen -t rsa -b 4096 -C \u0026ldquo;your_email@example.com\u0026rdquo;\ned25519 pertenece a una rama de la criptografía llamada \u0026ldquo;criptografía de curva elíptica (ECC)\u0026rdquo;. RSA se basa en matemáticas bastante sencillas (multiplicación de enteros), mientras que ECC procede de una rama de las matemáticas mucho más complicada llamada \u0026ldquo;teoría de grupos\u0026rdquo;. En resumen: las claves ECC pueden ser mucho más cortas y ofrecer el mismo nivel de seguridad porque el problema matemático en el que se basan es mucho más complejo.\nPara añadir una clave pública en github vete a https://github.com/settings/keys y añádela pulsando el botón verde de New SSH key.\nBonus track Para mover archivos al servidor remoto podemos utilizar el comando rsync, que se usaría de la siguiente forma:\nrsync -av {local_directory} {remote_user}@{remote_host}:{remote_directory}\n-a (archive) It is a quick way of saying you want recursion and want to preserve almost everything -v (verbose) This option increases the amount of information you are given during the transfer Para copiar el contenido de un fichero desde terminal al portapeles podemos ejecutar desde linux:\ncat {your_file.extension} | xsel \u0026ndash;clipboard \u0026ndash;input\nY para pegar el contenido del portapapeles sería ejecutar lo siguiente:\nxsel \u0026ndash;clipboard \u0026ndash;output\n","permalink":"https://raulpadilladelgado.github.io/blog/p/ssh-para-el-d%C3%ADa-a-d%C3%ADa/","summary":"\u003ch1 id=\"introducción\"\u003eIntroducción\u003c/h1\u003e\n\u003cp\u003eSSH o Secure Shell Protocol, es un protocolo de comunicación así como pueda serlo los muy conocidos HTTP, HTTPS o FTP.\u003c/p\u003e\n\u003cp\u003eEn el caso de SSH nos permite la comunicación entre dispositivos dentro de la red, así como controlar o modificar ordenadores remotos.\u003c/p\u003e\n\u003cp\u003e¿Que ofrece de nuevo entonces frente a otros protocolos de comunicación? Pues básicamente es un protocolo orientado a mejorar la seguridad de la comunicaciones encriptando los datos de modo que intrusos no puedan ver contenido protegido bajo el protocolo.\u003c/p\u003e","title":"SSH para el día a día"},{"content":"Java Virtual Machine (JVM) ¿Que es? La Máquina Virtual de Java, en inglés Java Virtual Machine (JVM), es un componente dentro de JRE (Java Runtime Environment) necesario para la ejecución del código desarrollado en Java, es decir, es la máquina virtual la que permite ejecutar código Java en cualquier sistema operativo o arquitectura. De aquí que se conozca Java como un lenguaje multiplataforma. JVM interpreta y ejecuta instrucciones expresadas en un código máquina especial (bytecode), el cual es generado por el compilador de Java (también ocurre con los generados por los compiladores de lenguajes como Kotlin y Scala). Dicho de otra forma, es un proceso escrito en C o C++ que se encarga de interpretar el bytecode generado por el compilador y hacerlo funcionar sobre la infraestructura de ejecución. Como hay una versión de la JVM para cada entorno que sí conoce los detalles de ejecución de cada sistema, puede utilizar el código máquina equivalente para cada una de las instrucciones bytecode.\nJava tiene dos componentes principales:\nJava Runtime Environment (JRE): es el entorno de ejecucción de Java. Incluye la máquina virtual (JVM), las librerías básicas del lenguaje y otras herramientas relacionadas. Java Delopment Kit (JDK); además del JRE, incluye el compilador, el debugger, el empaquetador JAR, herramientas para generar documentación, etc Por lo que cuando queremos ejecutar un fichero java ocurre lo siguiente:\nUn fichero example.java se compilar (gracias al compilador del JDK) y se genera un fichero example.class que contiene el bytecode capaz de ser interpretado por la JVM. Durante la ejecución del código, Class Loader se encarga de llevar los ficheros .class a las JVM que reside en la RAM del ordenador y ByteCode Verifier se encarga de verificar que el código en bytecode procede de una compilación válida El compilador just in time compila el bytecode a código nativo de la máquina y se ejecuta directamente En el siguiente apartado veremos más a fondo la definición de cada uno de estos subsistemas de la JVM.\nEstructura La JVM se descompone en 3 subsistemas:\nClass loader subsystem Cuando una clase Java necesita ser ejecutada, existe un componente llamado Java Class Loader Subsystem que se encarga de cargar, vincular e inicializar de forma dinámica y en tiempo de ejecución las distintas clases en la JVM. Se dice que el proceso es dinámico porque la carga de los ficheros se hace gradualmente, según se necesiten.\nCarga\nExisten a su vez tres tipos de Loaders y cada uno tiene una ruta predefinida desde donde cargar las clases.\nBootstrap/Primordial ClassLoader: es el padre de los loaders y su función es cargar las clases principales desde jre/lib/rt.jar, fichero que contiene las clases esenciales del lenguaje Extensión ClassLoader: delega la carga de clases a su padre (bootstrap) y, en caso fallido, las carga el mismo desde los directorios de extensión de JRE (jre/lib/ext) System/Application ClassLoader: es responsable de cargar clases específicas desde la variable de entorno CLASSPATH o desde la opción por línea de comandos -cp. Vínculo\nLinking es el proceso de añadir los bytecodes cargados de una clase en el Java Runtime System para que pueda ser usado por la JVM. Existen tres paso en el proceso de Linking, aunque el último es opcional.\nVerify: Bytecode Verifier comprueba que el bytecode generado es correcto. En caso de no serlo, se devuelve un errror. Prepare: una vez se ha verificado, se procede a asignar memoria a las variables de las clases y se inicializan con valores por defecto dependiendo de su tipo. Las variables de clases no toman su valor inicial correcto hasta la fase de Initialization. Valores por defecto de variables primitivas:\n➡️ int = 0\n➡️ long = 0L\n➡️ short = (short) 0\n➡️ char = \u0026ldquo;\\u0000\u0026rdquo;\n➡️ byte = (byte) 0\n➡️ boolean = false\n➡️ reference = null\n➡️ float = 0.0f\n➡️ double = 0.0d Resolve: JVM localiza las clases, interfaces, campos y métodos referenciados en una tabla llamada constant pool (CP) y determina los valores concretos a partir de su referencia simbólica. Cuando se compila una clase Java, todas las referencias a variables y métodos se almacenan en el CP como referencia simbólica. Una referencia simbólica, de forma muy breve, es un string que puede usarse para devolver el objeto actual. El CP es un área de memoria con valores únicos que se almacenan para reducir la redundancia. Para el siguiente ejemplo:\nSystem.err.println(\u0026quot;Test\u0026quot;);\nSystem.out.println(\u0026quot;Test\u0026quot;);\nen el CP solo habría un objeto, String \u0026ldquo;Test\u0026rdquo; Inicialización\nSe encarga de que las variables de clase se inicialicen correctamente, con los valores que el desallorador especificó en el código.\nRuntime Data Areas JVM define varias áreas de datos que se utilizan durante la ejecución de un programa y que se podrían dividir en dos grupos. Algunas de estas áreas se crean al inicializarse la JVM y se destruyen una vez la JVM finaliza (compartidas por todos los hilos). Otras se inicializan cuando el hilo se crea y se destruyen cuando el hilo se ha completado (una por hilo).\nMethod area: es parte de Heap Area. Contiene el esqueleto de la clase (métodos, constantes, variables, atributos, constructor, etc) Heap area: fragmento de memoria donde se almacenan los objetos creados (todo lo que se inicialice con el operador new). Si el objeto se borra, el Garbage Collector se encarga de liberar su espacio. Solo hay un Heap Area por JVM, por lo que es un recurso compartido (igual que Method Area) Stack area: fragmento de memoria donde se almacenan las variables locales, parámetros, resultados intermedios y otros datos. Cada hilo tiene una private JVM stack, creada al mismo tiempo que el hilo. PC register: contiene la dirección actual de la instrucción que se está ejecutando (una por hilo) Native Method Stack: igual que Stack, pero para métodos nativos, normalmente escritor en C o C++. Execution Engine El bytecode que es asignado a las áreas de datos en la JVM es ejecutado por el Execution Engine, ya que este puede comunicarse con distintas áreas de memoria de la JVM. El Execution Engine tiene los siguientes componentes.\nInterpreter: es el encargado de ir leyendo el bytecode y ejecutar el código nativo correspondiente. Esto afecta considerablemente al rendimiento de la aplicación. JIT Compiler: interactúa en tiempo de ejecucción con la JVM para compilar el bytecode a código nativo y optimizarlo. Esto permite mejorar el rendimiento del programa. Esto se hace a través del HotSpot compiler. Garbage Collector: libera zonas de memoria que han dejado de ser referenciadas por un objeto Empaquetar y ejecutar aplicación Java Ya hemos visto la parte teórica de como funciona la JVM y ahora toca realizar un ejemplo práctico.\nPara compilar una app usaremos el javac dentro del directorio bin de nuestra instalación\njavac MyApp.java Esto generar archivos .class a partir de nuestro archivo fuente .java. Estos son los archivos que puede ejecutar la máquina virtual de Java.\nPara ejecutar una aplicación usaremos java que lo podemos encontrar en el directorio bin del JRE o JDK. Para ello, debemos hacer referencia a una clase que contenga un método estático main, el principal punto de entrada de las aplicaciones en Java. Además, si forma parte de un paquete debemos escribir la ruta completa desde la base del árbol.\njava com.example.myApp.MyApp Java permite empaquetar las aplicaciones y librerías en archivos comprimidos. De esta forma es más sencillo poder reutilizar el código a través de distintas aplicaciones o desplegar nuevas versiones de la aplicación. Estos archivos pueden ser:\nJAR: librerías o aplicaciones de escritorio WAR: aplicaciones web jar cf jar-file files-to-package La opción c indica que se desea crear el archivo y la opción f especifica el nombre del archivo. Este comando genera un comprimido .jar que contiene todas las clases que indiquemos, incluyendo directorios de forma recursiva. Además, genera un archivo de manifiesto. Si el archivo de manifiesto especifica el header Main-Class, podremos ejecutar la aplicación desde el archivo JAR de la siguiente forma:\njava -jar jar-file Los archivos JAR también pueden ser agregados al classpath, de forma que las aplicaciones puedan obtener sus dependencias al explorar dentro de su contenido. Es la principal forma de distribución de librerías. Normalmente, cuando descargamos una aplicación Java, esta trae sus propios JAR además de las dependencias.\nGraalVM Hablamos de una Virtual Machine que es una extensión de la JVM tradicional que permite ejecutar cualquier lenguaje en una única VM (JavaScript, R, Ruby, Python\u0026hellip;). Soporta modos de ejecucción tales como la compilación ahead-of-time que permite un tiempo de arranque más rápido en aplicaciones Java, resultado en ejecutables que ocupan menos memoria.\n🎯 Objetivos\nMejorar el rendimiento de los lenguajes basados en la máquina virtual de Java haciendo que tengan un rendimiento similar a los lenguajes nativos Reducir el tiempo de arranque de las aplicaciones de la JVM mediante la compilación ahead-of-time (antes de tiempo) con GraalVM Native image Permitir la integración de GraalVM en Oracle Database, Node.js, Android/iOS y otros 📰 Lenguajes y runtimes\nGraalVM JavaScript: runtime de JavaScript (ECMAScript 2019), con soporte para Node.js TruffleRuby: implementación de Ruby FastR: implementación del lenguaje R 🔩 Componentes\nGraaVM Compiler: se trata de un compilador JIT para Java GraalVM Native Image: permite la compilcación ahead-of-time Truffle Language Implementation Framework: depende de GraalVM SDK y permite implementar otros lenguajes en GraalVM Instrumentation-based Tool Support: soporte para instrumentación dinámica, que es agnóstica del lenguaje Una opción que puede ser interesante, llegando a posibilidad de tener varios lenguajes dentro de un mismo proyecto, y que tiene el suficiente cuerpo como para que sea comentada en otro post del blog.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/java-virtual-machine/","summary":"\u003ch1 id=\"java-virtual-machine-jvm\"\u003eJava Virtual Machine (JVM)\u003c/h1\u003e\n\u003ch2 id=\"que-es\"\u003e¿Que es?\u003c/h2\u003e\n\u003cp\u003eLa Máquina Virtual de Java, en inglés Java Virtual Machine (JVM), es un componente dentro de JRE (Java Runtime Environment) necesario para la ejecución del código desarrollado en Java, es decir, es la máquina virtual la que permite ejecutar código Java en cualquier sistema operativo o arquitectura. De aquí que se conozca Java como un lenguaje multiplataforma. JVM interpreta y ejecuta instrucciones expresadas en un código máquina especial (bytecode), el cual es generado por el compilador de Java (también ocurre con los generados por los compiladores de lenguajes como Kotlin y Scala). Dicho de otra forma, es un proceso escrito en C o C++ que se encarga de interpretar el bytecode generado por el compilador y hacerlo funcionar sobre la infraestructura de ejecución. Como hay una versión de la JVM para cada entorno que sí conoce los detalles de ejecución de cada sistema, puede utilizar el código máquina equivalente para cada una de las instrucciones bytecode.\u003c/p\u003e","title":"Java Virtual Machine"},{"content":"Introducción Recientemente he estado trabajando en un proyecto personal que surge por la necesidad de una función específica en spotify, que no podía conseguir a través de la app oficial de la plataforma. Mi objetivo era poder organizar mis playlist por orden de fecha de estreno de la canción. Spotify te permite organizarlas por fecha en la que la añadiste a la playlist, pero no por el tipo de organización que yo quería.\nEntonces fue cuando descubrí Sort Your Music, que es una aplicación web que te ofrece herramientas para organizar tus playlist de una forma más avanzada.\nGracias a la anterior web me decidí por montar mi propia implementación que tenga lo que me gusta y necesito, y montarlo en algo que de un modo rápido me permita lanzar mi comando de organizar playlists.\nAhora mismo, el proyecto se trata de una aplicación Python sencilla, que hace uso de la librería spotipy, que nos simplifica la forma en la que interactuamos con la API de Spotify. Al arrancar la aplicación, seremos capaces de listar las playlist del usuario y ordenar una determinada playlist por fecha de estreno de las canciones en orden descendente.\nPara ofrecer una interfaz al usuario se ha utilizado Flask, un framework ligero que permite montar un servidor web con pocas líneas de código.\nEl repositorio lo puedes encontrar en:\nraulpadilladelgado/toolify (github.com)\nLa aplicación web la puedes usar en:\nhttps://toolify-app.herokuapp.com\nSpotify for developers Para que podamos utilizar la API de Spotify necesitamos:\nCrearte una cuenta en Spotify Developers e iniciar sesión\nNo es necesario crear una cuenta si inicias sesión con la cuenta que habitualmente usas para Spotify Es hora de ir a nuestro dashboard y crear una nueva APP Una vez creada la APP, es necesario localizar tu client_id y client_secret, pues serán las claves para que podamos realizar operaciones utilizando la API de Spotify. En el README del proyecto se muestra donde se deben incluir estos valores. Definir la ruta de redirección. La primera vez que un usuario interactua con la aplicación que hemos creado es necesario que se autentifique y que verifique que nos da permiso para alterar cierta información relacionada a sus playlists a un scope determinado. Esta ruta de redirección sirve para que una vez se finalice correctamente el proceso de dar acceso, el token generado se guarde. Debemos ir a \u0026ldquo;Edit Settings\u0026rdquo;(vease en la imagen anterior), y tener definido lo siguiente: Ejecutar Toolify Cuando montas el proyecto por primera vez, es necesario instalar SpotyPy pip install -r requirements.txt\nLas claves de la APP de Spotify Developers que habíamos comentado en el apartado anterior, serán introducidas como variables del sistema operativo. TOOLIFY_SECRET_KEY SPOTIFY_REDIRECT_URI SPOTIFY_CLIENT_SECRET SPOTIFY_CLIENT_ID\nFinalmente, puedes arrancar la aplicación usando: flask run Siguientes pasos En esta primera iteración con el proyecto he ofrecido la posibilidad de ordenar el contenido de una playlist por fecha de estreno de las canciones, entonces surge una pregunta, ¿como explotar el proyecto?\nMe gusta mucho crear mis propias playlists, y para mi el orden es fundamental para que cuando esté escuchando música vayan apareciendo las canciones en una secuencia de mi interés, como puede ser la fecha de estreno de las canciones o el BPM de la canción si estás buscando que las canciones más marchosas estén primero. Jugando con como ordenamos las canciones se puede ofrecer una experiencia totalmente distinta.\nEn una playlist no todo es ordenar, a veces se duplican canciones o se introducen versiones editadas de las canciones originales (lo que se conoce como Remix) e interesa solo mantener la última versión. Sería interesante que Toolify identificase estos casos, que los muestre al usuario para que decida que mantener, y finalmente que la aplicación borre lo que no interese.\nPodríamos ser capaces de mostrar al usuario sus estadísticas de uso en spotify, sus cantantes más escuchados, canciones más escuchadas, entre otros datos de interés.\nEsto es todo, ¡muchas gracias por leer!\n","permalink":"https://raulpadilladelgado.github.io/blog/p/toolify/","summary":"\u003ch1 id=\"introducción\"\u003eIntroducción\u003c/h1\u003e\n\u003cp\u003eRecientemente he estado trabajando en un proyecto personal que surge por la necesidad de una función específica en spotify, que no podía conseguir a través de la app oficial de la plataforma. Mi objetivo era poder organizar mis playlist por orden de fecha de estreno de la canción. Spotify te permite organizarlas por fecha en la que la añadiste a la playlist, pero no por el tipo de organización que yo quería.\u003c/p\u003e","title":"Toolify"},{"content":"Introducción Este post trata de exponer una serie de buenas prácticas o trucos a la hora de realizar testing de código, y forma parte de una serie de capítulos que pretenden seguir con el propósito.\nEn esta primera iteración la idea es hablemos sobre TDD, programación funcional, patrones de diseño y estabilidad, todo esto orientado a los tests.\nVamos al laboratorio! 🧪\nTDD en nuestros tests Test-Driven Development (TDD) es una práctica de programación que consiste en escribir primero las pruebas, después escribir el código fuente que pase la prueba satisfactoriamente y, por último, refactorizar el código escrito.\nCon esta práctica se consigue entre otras cosas: un código más robusto, más seguro, más mantenible y una mayor rapidez en el desarrollo. Además, logramos realizar pruebas más sencillas, ya que escribimos código productivo para nuestros tests, y no al revés. Ganamos en código simple y fácil de testear.\nPara ver como el TDD mejora nuestros tests veamos un ejemplo siguiendo la práctica:\nKata fizzbuzz Enlace a la kata: https://www.hackerrank.com/challenges/fizzbuzz/problem\nRecomiendo que el siguiente ejercicio se haga siguiendo la metodología TDD.\nTodo comienza por un test que falla porque aún no tenemos implementado que hará la función a testear. Le escribes el mínimo código para que cumplamos nuestro test. Refactorizamos con la tranquilidad de que sabremos si estamos cambiando comportamiento en el código gracias a la ejecución de nuestro test Básicamente, el problema nos propone lo siguiente:\nPasamos un número, y puede ocurrir lo siguiente: - Que sea divisible por tres y devuelva \u0026#34;fizz\u0026#34; - Que sea dividible por cinco y devuelva \u0026#34;buzz\u0026#34; - Que no sea dividible por ninguno de los anteriores y devuelva el mismo número - Que sea dividible por tres y por cinco y devuelva \u0026#34;fizzbuzz\u0026#34; Manos a la obra! 👷🏻\nCaso: Que no sea dividible por ninguno de los anteriores y devuelva el mismo número\nTest\n@Test public void return_the_same_number_as_passed(){ assertThat(Fizzbuzz.fizzbuzz(1)).isEqualTo(\u0026#34;1\u0026#34;); } Code\nclass Fizzbuzz{ public static String fizzbuzz(int number){ return String.valueOf(number); } De momento solo tenemos un caso de uso, asi que nos ceñimos a desarrollar lo mínimo\nCaso: Que sea dividible por tres y devuelva \u0026ldquo;fizz\u0026rdquo;\nTest\n@Test public void return_the_same_number_as_passed(){ assertThat(Fizzbuzz.fizzbuzz(1)).isEqualTo(\u0026#34;1\u0026#34;); } @Test public void return_fizz_if_is_divisible_by_3(){ assertThat(Fizzbuzz.fizzbuzz(3)).isEqualTo(\u0026#34;fizz\u0026#34;); } Code\nclass Fizzbuzz{ public static String fizzbuzz(int number){ if(number % 3 == 0){ return \u0026#34;fizz\u0026#34;; } return String.valueOf(number); } } Progresivamente vamos añadiendo lógica para los nuevos casos de uso sin romper los anteriores\nCaso: Que sea dividible por cinco y devuelva \u0026ldquo;buzz\u0026rdquo;\nTest\n@Test public void return_the_same_number_as_passed(){ assertThat(Fizzbuzz.fizzbuzz(1)).isEqualTo(\u0026#34;1\u0026#34;); } @Test public void return_fizz_if_is_divisible_by_3(){ assertThat(Fizzbuzz.fizzbuzz(3)).isEqualTo(\u0026#34;fizz\u0026#34;); } @Test public void return_buzz_if_is_divisible_by_5(){ assertThat(Fizzbuzz.fizzbuzz(5)).isEqualTo(\u0026#34;buzz\u0026#34;); } Code\nclass Fizzbuzz{ public static String fizzbuzz(int number){ if(number % 3 == 0){ return \u0026#34;fizz\u0026#34;; } if(number % 5 == 0){ return \u0026#34;buzz\u0026#34;; } return String.valueOf(number); } } Progresivamente vamos añadiendo lógica para los nuevos casos de uso sin romper los anteriores\nCaso: Que sea dividible por tres y por cinco y devuelva \u0026ldquo;fizzbuzz\u0026rdquo;\nTest\n@Test public void return_the_same_number_as_passed(){ assertThat(Fizzbuzz.fizzbuzz(1)).isEqualTo(\u0026#34;1\u0026#34;); } @Test public void return_fizz_if_is_divisible_by_3(){ assertThat(Fizzbuzz.fizzbuzz(3)).isEqualTo(\u0026#34;fizz\u0026#34;); } @Test public void return_buzz_if_is_divisible_by_5(){ assertThat(Fizzbuzz.fizzbuzz(5)).isEqualTo(\u0026#34;buzz\u0026#34;); } @Test public void return_fizzbuzz_if_is_divisible_by_3_and_5(){ assertThat(Fizzbuzz.fizzbuzz(15)).isEqualTo(\u0026#34;fizzbuzz\u0026#34;); } Code\nclass Fizzbuzz{ public static String fizzbuzz(int number){ if (number % 3 == 0 \u0026amp;\u0026amp; number % 5 == 0){ return \u0026#34;fizzbuzz\u0026#34;; } if(number % 3 == 0){ return \u0026#34;fizz\u0026#34;; } if(number % 5 == 0){ return \u0026#34;buzz\u0026#34;; } return String.valueOf(number); } } El ir implementando cada caso poco a poco con la lógica necesario para superar solo ese caso, ha hecho que lleguemos a una solución sencilla, y que escala a medida que necesitamos más casos de uso.\nTest más legible, test más comestible 🍽 Programación declarativa frente a imperativa Cuando hacemos uso de programación declarativa le estamos diciendo al código que haga algo, pero no el cómo lo tiene que ser, nosotros solo le pedimos una misión. Un ejemplo de este tipo de comportamiento es el lenguaje SQL, que por defecto funciona de tal manera. Si queremos buscar un cliente haríamos lo siguiente:\n1 SELECT * FROM clients WHERE name = \u0026#39;name\u0026#39;; Se ve muy fácil de leer, sobre todo porque no le indicamos como tiene que conseguir el propósito, eso son aspectos internos del propio de lenguaje.\nPor el contrario, con programación imperativa no le decimos que queremos obtener, sino que nosotros mismo implementamos esa funcionalidad usando un lenguaje como pueda ser java. Siguiendo el ejemplo anterior para buscar un cliente dentro una lista tendríamos lo siguiente usando programación imperativa:\n1 2 3 4 5 6 for (i = 0; i \u0026lt; clients.length(); i++) { Client client = client.get(i); if (client.name.equals(clientToBeFind)){ return client; } } O extrapolado a hacerte un café sería:\nProgramación declarativa\nPrepara un café Programación imperativa\nVe a la cocina Échale agua a la cafetera, cafe y ponla al fuego Espera a que salga el café y tráelo El gran pro de usar la programación declarativa es la transparencia que te da de lo que está ocurriendo, te estás abstrayendo de toda complejidad existente. Además, no solo sabes que está pasando en todo momento de una forma más clara, sino que ganas en código impoluto, ya que mandar instrucciones a la aplicación será como leer un cuento.\nEl paradigma de la programación funcional con la declarativa Como mencioné anteriormente, SQL funciona de tal forma que es declarativo, pero también tenemos otros lenguajes como Javascript que aunque no es declarativo, tiene muchas funciones predefinidas que llaman a utilizar la programación funcional. En Javascript el modo de iterar listas o arrays puede ser un proceso que sin terminar de ser declarativo del todo, es mucho más amigable que en otros lenguajes. Por ejemplo:\n1 2 const ages = [12,32,32,53] const selectedAges = ages.map(age =\u0026gt; age += 10).filter(age =\u0026gt; age \u0026gt; 40); Como vemos tiene mucho parecido con la programación declarativa, ya que seguimos pidiendo cosas sin importar como las haga, como pueda ser el uso de map o filter, pero en el caso de map le tenemos que indicar que realizar con cada elemento de la lista, y en el caso de filter que elementos debe escoger y cuáles no. No termina de ser declarativo, pero nos permite tener esa limpieza abstrayéndonos de bucles complicados.\nEste comportamiento de las funciones de javascript responde a que son funciones inmutables, es decir, que no alteran ningún aspecto de la aplicación como pueda ser el estado de un atributo de un objeto, una variable global, o lo que sea. Son funciones puras, por lo que siempre que pongamos la misma entrada saldrá la misma salida. Siempre devuelven el resultado de llamarlas por eso es por lo que funcionan como la programación funcional.\nPatrones de diseño aplicados a testing El uso de patrones de diseño en código productivo se hace con la finalidad de que nuestro código sea más mantenible, escalable, siga unas reglas de diseño que busquen organización y otros fines similares. Pero cuando se trata de test, estos no son una excepción. Nuestras pruebas automatizadas también pueden seguir unos patrones de diseño con el fin de mejorar la mantenibilidad y legibilidad.\nAhora veremos algunos ejemplos escritos en Kotlin sobre como mejorar la legibilidad y mantenibilidad de nuestros test mediante el uso de build pattern, object mother o named arguments. Vamos a poner en contexto una clase llamada User, en la que tenemos una regla de negocio que para apostar se debe tener una edad mínima.\nTradicional 1 2 3 4 5 6 7 8 9 10 11 private val minimumAgeToBet = 18 fun canBet(): Boolean { return userAge \u0026gt;= minimumAgeToBet } fun `user can bet with the minimum age`() { val userAge = 22 val user = User(\u0026#34;some name\u0026#34;, userAge) assertTrue(user.canBet()) } En este primer caso hemos creado directamente una instancia de la clase en nuestros test, por lo que cada uno tendrá la responsabilidad de mantenerse actualizado si la firma de la clase cambia, como por ejemplo que se añada un parámetro.\nBuilder Pattern 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 fun withAge(age: Int): UserBuilder { userAge = age return this } fun build(): User { return User(userName, userAge) } fun `user can bet with the minimum age`() { val userAge = 22 val user = UserBuilder().withAge(20).build() assertTrue(user.canBet()) } En este caso hemos hecho uso del patrón builder, que implicar tener un Builder encargado de construir nuestra clase e inicializarla a través de un build().\nEn el constructor habrá definidos unos valores por defecto para todos los atributos, y proporcionarnos unos setters que sirven para aportar semántica al test, ya que a la hora de establecer valores solo lo haremos en aquellos atributos que tiene importancia en el test.\nUsando el patrón builder hemos sacado fuera de los test la responsabilidad de crear instancias de las clases, hemos ganado semántica y hemos hecho más declarativos nuestros tests, ahora muestran de una forma más clara que les interesa.\nEl patrón builder gana al método tradicional, pero tiene un problema, cuando tengamos muchos atributos a los que definirles un valor vamos a pasarlo un poco mal, veamos otro patrón que nos será aún más útil.\nObject mother 1 2 3 4 5 6 7 8 9 10 11 companion object { fun userWithMinimumAgeToBet(): User { return User(\u0026#34;name\u0026#34;, 22) } } fun `user can bet with the minimum age`() { val user = UserFixtures.userWithMinimumAgeToBet() assertTrue(user.canBet()) } En el ámbito de test hacemos uso del ObjectMother, que no es más que una clase de factoría que contiene una serie de métodos estáticos que nos permiten crear una instancia de la clase con mayor semántica. Hemos indicado el tipo de caso que queremos, sin entrar en detalles de cuál tiene que ser el valor exacto, por lo que hemos ganado en semántica y además tenemos un factor a nuestro favor y es que si mañana cambia la edad mínima o la forma en la que y en varios de nuestros test llamamos al método userWithMinimumAgeToBet, solo tendremos que cambiar o añadir variables o lo que haga falta en el UserFixtures, nuestro test no necesita ser modificado.\nProgramación funcional en nuestros test Ahora voy a exponer un ejemplo real escrito en Kotlin de como el uso de programación declarativa junto con el patrón builder han sido una gran ventaja a la hora de desarrollar test que necesitaban realizar muchas aserciones para verificar el estado de registros persistidos en la base de datos.\nPongamos en contexto un contrato que tiene como propiedades:\nEstado Fecha de alta Fecha de baja Servicio contratado Teniendo lo anterior presente, vamos a crear una clase que nos permita construir paso a paso lo que queremos comprobar. Lo que queremos verificar son las columnas de ciertas filas en base de datos, o lo que es lo mismo, queremos revisar la información almacenada para un pedido, por lo que cuando produzcamos una instancia de la clase que nos hará las aserciones le pasaremos el ID en cuestión. Por otro lado, iniciaremos un mapa en el que la clave del mapa será la columna en la tabla de base de datos y el valor del mapa será el valor en la tabla de base de datos para dicha columna.\nTendremos múltiples métodos para añadir columnas a comprobar, y cada uno de estos métodos devuelve la misma instancia de la clase para que podamos ir concatenando llamadas y agregar más columna que comprobar.\nFinalmente, la última llamada que realizaremos será al método que hará las aserciones.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class OrderAssertion(private val orderId: Long, private val connection: DBConnection) { private val fieldsValuesMap: MutableMap\u0026lt;String, String\u0026gt; = mutableMapOf() fun hasStatus(status: String): OrderAssertion { fieldsValuesMap[\u0026#34;status\u0026#34;] = status return this } fun hasOrderDate(date: String): OrderAssertion { fieldsValuesMap[\u0026#34;order_date\u0026#34;] = date return this } fun hasProducts(products: String): OrderAssertion { fieldsValuesMap[\u0026#34;products\u0026#34;] = products return this } fun doAssert() { try { assertOrder() } catch (assertionError: AssertionError) { throw assertionError } catch (unexpectedError: Exception) { throw AssertionError(\u0026#34;Unexpected Error inside order assert\u0026#34;, unexpectedError) } } @Throws(Exception::class) private fun assertOrder() { val sql = StringBuilder(500) sql.append(\u0026#34;SELECT id\u0026#34;) for (field in fieldsValuesMap.keys) { sql.append(\u0026#34;, \u0026#34;).append(field) } sql.append(\u0026#34; FROM orders\u0026#34;).append(\u0026#34; WHERE id = \u0026#34;).append(orderId) connection.executeSQL(sql.toString()).use { results -\u0026gt; if (results.moveNext()) { for ((field, expected) in fieldsValuesMap) { val value: String = results.getString(field) assertThat(value) .withFailMessage( \u0026#34;Wrong value for field \u0026#39;%s\u0026#39;. Expected is \u0026#39;%s\u0026#39; but actual was \u0026#39;%s\u0026#39;\u0026#34;, field, expected, value ).isEqualTo(expected) } } else { fail(\u0026#34;Order with ID \u0026#39;%s\u0026#39; not found\u0026#34;, orderId) } } } } Nuestro test quedará muy claro y limpio, gracias al trabajo que hemos hecho en esta clase de aserciones que quita toda complejidad innecesaria para nuestro test.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 fun `create a order`() { val orderId = \u0026#34;1234\u0026#34; val status = \u0026#34;in arrival\u0026#34; val orderDate = \u0026#34;2021-05-31 00:00:00\u0026#34; val products = \u0026#34;some products\u0026#34; orderService.makeOrder(orderId, status, products) assertThatOrderWithId(orderId) .hasStatus(status) .hasOrderDate(orderDate) .hasProducts(products) .doAssert() } Como si de un framework se tratara, hemos simplificado y abstraído del test la complejidad de hacer aserciones a la base de datos, y ha quedado un test muy fácil de leer. Así es, test legible, test más comestible.\nEstabilidad en nuestros test Como solucionar test que fallan aleatoriamente Un problema que nos podemos encontrar a la hora de hacer test, es que tenemos algunos tests que algunas veces pasan y otras no. Esto se puede deber a que tenemos una batería de test en la que el orden de ejecución influye en el resultado de los mismos. El ejemplo más claro sería un test que persiste un registro y otro test que intenta recuperar ese mismo registro. Un test no debería depender de otros para poder funcionar correctamente, por lo que cada test debería comenzar desde cero y levantar o preparar todo lo que necesite para el caso de uso que esté probando, sin esperar que sea otro test el que prepare el escenario.\nPor lo que tengo entendido, en JUnit (framework de unit test para JVM) la primera ejecución de los test en una máquina (tu pc) se hace forma aleatoria, pero después ejecutará los mismos test siempre en el mismo orden. Eso quiere decir que si otra máquina ejecuta esos mismos test por primera vez también será de manera aleatoria, pero solo una vez, el resto de veces se ejecutarán en el mismo orden. Tenemos un modo de indicar usando JUnit que queremos que la ejecución de nuestros test sea de modo aleatorio, y así asegurarnos que ninguno de nuestros test depende de otro. Simplemente, lograremos esta aleatoriedad estableciendo una etiqueta encima del nombre de la clase de test:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 @TestMethodOrder(MethodOrderer.Random::class) internal class SomeTest { @Test fun aTest() { } @Test fun aTest() { } @Test fun aTest() { } } Como forzar tests deterministas Otro punto de error que puede surgir a la hora que realizamos test, es tener aserciones que dependen de variables y que además tienen mucha tendencia a cambiar. Es decir, si en un test estamos comprobando el retorno de una función contra una variable el test pasará en ocasiones si y en otras no. Esto se debe a que comparar el resultado contra dicha variable no es la mejor idea, ya que dicha variable estará cambiando su valor constantemente y, por tanto, no tendremos un test consistente y estará lanzándonos un error cuando puede ser que en realidad la función que estamos testeando está perfecta. Vamos a ver un ejemplo en Kotlin muy sencillo con una función que tiene que comprobar si en este momento actual es de día:\nPor un lado, el método check() de la clase MorningChecker que devuelve true o false dependiendo de si es por la mañana o no.\n1 2 3 4 5 6 7 8 class MorningChecker { fun isMorning(): Boolean { val hour: Int = now().hour val startOfMorning = 6 val endOfMorning = 12 return hour in startOfMorning..endOfMorning } } Por otro lado, el test que comprueba que cuando es por la mañana debería devolver true\n1 2 3 4 5 6 7 class MorningCheckerShould { @Test fun `check if is morning`() { val morningChecker = MorningChecker() assertTrue(morningChecker.isMorning()) } } Si estamos lanzando el test entre las 6 y las 12 nos dará un resultado correcto, pero en cualquier otro horario este test no pasaría. Nuestro método isMorning() está sacando la variable que determina que hora del día es usando una librería que te la proporciona en tiempo real, por lo que cuando vamos al test también se usa la fecha real. Vamos a cambiar un poco el panorama para que en vez de usar una variable calculada basándose en el tiempo real, usemos un parámetro:\nExtraemos a un parámetro la hora del día\n1 2 3 4 5 6 7 class MorningChecker { fun isMorning(hour: Int): Boolean { val startOfMorning = 6 val endOfMorning = 12 return hour in startOfMorning..endOfMorning } } Y finalmente en el test le pasamos la hora que queremos comprobar para ese test\n1 2 3 4 5 6 7 8 9 class MorningCheckerShould { @Test fun `check if is morning`() { val morningChecker = MorningChecker() val actualHour = 8 assertTrue(morningChecker.isMorning(actualHour)) } } Ahora que el test tiene siempre la misma entrada, por ende tendrá siempre la misma salida. Sin sorpresas, solo lo que esperamos. Puede parecer un caso simple, pero la idea es plasmar que nuestro test no debería depender nunca de factores variables, que hacen que perdamos el control de lo que pasa y que en ocasiones provoquen que nuestro test no tenga siempre el mismo resultado para la misma entrada.\nEstructura de los test Cuando hablamos de entender un test no solo está sobre la mesa su contenido, sino que su contexto también no es de utilidad y muy importante que esté en su lugar correcto. Una buena organización y estructura de los archivos del proyecto es fundamental, pero si aplicamos arquitectura hexagonal vamos un paso más allá, ya que estaremos separando responsabilidades por capas y estaremos mejorando la mantenibilidad y escalabilidad de nuestro código. Pero la arquitectura hexagonal no exclusivamente la podemos aplicar a nuestro source code (código productivo), deberíamos seguir la misma idea para nuestro test, separándolos entre test que comprueban aspectos de la aplicación, el dominio o la infraestructura. Para aprender maś sobre arquitectura hexagonal puede ver un post realizado por mí:\nhttps://raulpadilladelgado.github.io/blog/p/arquitectura-hexagonal/\n","permalink":"https://raulpadilladelgado.github.io/blog/p/buenas-pr%C3%A1cticas-en-testing-cap.1/","summary":"\u003ch1 id=\"introducción\"\u003eIntroducción\u003c/h1\u003e\n\u003cp\u003eEste post trata de exponer una serie de buenas prácticas o trucos a la hora de realizar testing de código, y forma parte de una serie de capítulos que pretenden seguir con el propósito.\u003c/p\u003e\n\u003cp\u003eEn esta primera iteración la idea es hablemos sobre TDD, programación funcional, patrones de diseño y estabilidad, todo esto orientado a los tests.\u003c/p\u003e\n\u003cp\u003eVamos al laboratorio! 🧪\u003c/p\u003e\n\u003ch1 id=\"tdd-en-nuestros-tests\"\u003eTDD en nuestros tests\u003c/h1\u003e\n\u003cp\u003eTest-Driven Development (TDD) es una práctica de programación que consiste en escribir primero las pruebas, después escribir el código fuente que pase la prueba satisfactoriamente y, por último, refactorizar el código escrito.\u003c/p\u003e","title":"Buenas prácticas en testing (Cap.1)"},{"content":"Introducción ¿Que es una regex?\nRegex hace referencia a “expresión regular” y se trata de la técnica que nos permite hacer búsquedas de una secuencia de caracteres atendiendo a un patrón de búsqueda.\nEl uso de expresiones regulares nos puede simplificar y acelerar el proceso de tener que reemplazar varios textos que entre sí cumplen un patrón coincidente. Veamos un ejemplo para que sea más sencillo, supongamos que tenemos un escrito en el que varias personas se están presentando, por ejemplo:\nHola, soy Raúl y tengo 20 años Hola, soy María y tengo 25 años Hola, soy Marcos y tengo 1 años Cuando una persona tenga un año de edad deberíamos usar el singular en la palabra “años”, por lo que con una expresión regular vamos a modificar esa palabra para que nos sirva en ambos casos.\nLa expresión regular para buscar coincidencias [s]$ Lo que coincide (entre *) Hola, soy Raúl y tengo 20 año**s** Hola, soy María y tengo 25 año**s** Hola, soy Marcos y tengo 1 año**s** La expresión regular para reemplazar texto ($0) Lo que se reemplaza/añade/quita (entre *) Hola, soy Raúl y tengo 20 año**(s)** Hola, soy María y tengo 25 año**(s)** Hola, soy Marcos y tengo 1 año**(s)** O visto en formato GIF desde el IDE IntelliJ haciendo un reemplazo de texto usando el shortcut\nCtrl + r:\nAntes de entrar en materia y ver como utilizar esta herramienta de reemplazo de texto, vamos a repasar los tipos de match que podemos usar para construir nuestras expresiones regulares.\nTipos de match Caracteres Characters Legend Regex Example Coincidence Example \\d Dígitos del uno al nueve file_\\d\\d file_25 \\w Letra, dígito o barra baja \\w-\\w\\w\\w A-b_1 \\s Espacios, tabuladores, saltos de línea. a\\sb\\sc a bc \\D Un caracter que no sea un número \\D\\D\\D ABC \\W Un caracter que no sea un número ni una letra \\W\\W\\W\\W\\W *-+=) \\S Un caracter que no sea un espacio \\S\\S\\S\\S Yoyo . Cualquier caracter excepto salto de línea .* whatever, man. Escapes a special character .*+? $^/\\ .*+? $^/\\ \\t Tab T\\t\\w{2} T ab \\r Carriage return character see below \\n Line feed character see below \\r\\n Line separator on Windows AB\\r\\nCD ABCD [ … ] Uno de los caracteres dentro de los corchetes [AEIOU] One uppercase vowel [ … ] Uno de los caracteres dentro de los corchetes T[ao]p Tap or Top [ … ] Uno de los caracteres dentro de los corchetes [AB1-5w-z] One of either: A,B,1,2,3,4,5,w,x,y,z - Indicador de rango [a-z] One lowercase letter [x-y] Un caracter dentro del rango de x a y [A-Z]+ GREAT [ -~]+ Characters in the printable section of the ASCII table. [^x] Un caracter que no es una x [^a-z]{3} A1! [^x-y] Un caracter que NO está dentro del rango de x a y [^ -~]+ Characters that are not in the printable section of the ASCII table. [\\d\\D] Un caracter que es númerico o no numérico [\\d\\D]+ Any characters, inc-luding new lines, which the regular dot doesn’t match Cuantificadores Characters Legend Regex Example Coincidence Example + Uno o más veces Version \\w-\\w+ Version A-b1_1 {3} Exactamente tres veces \\D{3} ABC {2,4} De dos a cuatro veces \\d{2,4} 156 {3,} Tres o más veces \\w{3,} regex_tutorial * Cero o más veces A_B_C* AAACC ? Una o ninguna vez plurals? plural Lógica Characters Legend Regex Example Coincidence Example Alternación (tipicamente OR) 22 ( … ) Capturar grupos A(nt pple) \\1 Contenido del grupo 1 r(\\w)g\\1x regex \\2 Contenido del grupo 2 (\\d\\d)+(\\d\\d)=\\2+\\1 12+65=65+12 (?: … ) Grupo que no coincide A(?:nt pple) Anclajes y límites Characters Legend Regex Example Coincidence Example ^ Inicio de una línea ^abc .* abc (line start) $ Final de una línea .*? the end$ this is the end Modificadores en línea Characters Legend Regex Example Coincidence Example (?i) no distingue entre mayúsculas y minúsculas (?i)Monday monDAY (?s) Con este modificador el . también reconocerá saltos de línea (?s)From A.*to Z From Ato Z Herramienta de reemplazo de texto de IntelliJ Vistazo a las opciones En un fichero que contenga texto, combina Ctrl + r para abrir el menú de reemplazo:\nEn la primera fila del menú podemos apreciar:\n: En primer lugar vemos el icono de una lupa en la que si pulsamos sobre ella veremos el historial de los patrones de búsqueda que hemos ido introduciendo\n: Al lado de la lupa nos encontramos el campo donde introduciremos nuestro patrón de búsqueda\n: Seguido tenemos una flecha que si pulsamos sobre ella introduciremos un salto de línea\nCc: Define si el patrón de búsqueda debe distinguir entre mayúsculas y minúsculas o no\nW: ???\n.*: Define si la búsqueda será utilizando expresiones regulares o texto normal\nx/x: El contador de resultado de la búsqueda\n: Mover al resultado superior\n: Mover al resultado inferior\n: Ver los resultado en una ventana de búsqueda aparte\n: Seleccionar el texto de todas las coincidencias y cerrar herramienta de reemplazo Buscar solo en la selección de texto actual\nBuscar solo en la opción que elijamos (todo el texto, comentarios, etc)\nEn la segunda fila del menú podemos apreciar:\n: En primer lugar vemos el icono de una lupa en la que si pulsamos sobre ella veremos el historial de los patrones de reemplazo que hemos ido introduciendo\n: Al lado de la lupa nos encontramos el campo donde introduciremos nuestro patrón de reemplazo\n: Seguido tenemos una flecha que si pulsamos sobre ella introduciremos un salto de línea\nA'A: La activamos si queremos reemplazar texto manteniendo las mayúsculas y minúsculas tal como las encontramos\nReplace: Reemplazar la ocurrencia seleccionada\nReplace All: Reemplazar todas las ocurrencias\nExclude: Excluir de ser reemplazada la ocurrencia seleccionada\nReemplazar texto usando Regex Reemplazo directo: Se trata de introducir directamente el texto que queremos se reemplaze con el que tenemos en cada coincidencia. Reemplazo usando único grupo: Cuando escribimos expresiones regulares sin agruparlas, es decir, sin introducirlas dentro de unos parentesis. A pesar de que en este caso no hemos agrupado dentro de nuestra expresión regular, tenemos un grupo al que podemos referirnos ($0) y se trata de cada coincidencia entera. Reemplazo usando grupos definidos: Cuando agrupamos dentro de nuestra expresión regular podemos realizar reemplazos de textos más avanzados. Tendremos el grupo $0 que hace referencia a cada coincidencia entera, y después tendremos $1, $2…, así por cada grupo creado. ","permalink":"https://raulpadilladelgado.github.io/blog/p/multi-reemplazo-de-texto-usando-regex-en-intellij/","summary":"\u003ch1 id=\"introducción\"\u003eIntroducción\u003c/h1\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003e¿Que es una regex?\u003c/strong\u003e\u003cbr\u003e\nRegex hace referencia a “expresión regular” y se trata de la técnica que nos permite hacer búsquedas de una secuencia de caracteres atendiendo a un patrón de búsqueda.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eEl uso de expresiones regulares nos puede simplificar y acelerar el proceso de tener que reemplazar varios textos que entre sí cumplen un patrón coincidente. Veamos un ejemplo para que sea más sencillo, supongamos que tenemos un escrito en el que varias personas se están presentando, por ejemplo:\u003c/p\u003e","title":"Multi-reemplazo de texto usando Regex en IntelliJ"},{"content":"Introducción Arquitectura de software\nReglas autoimpuestas al definir como diseñamos software\n¿Que ganamos entonces imponiéndonos este tipo de reglas?\nBuscamos la mantenibilidad: Somos capaces de mantener mejor el código gracias a como formamos la arquitectura Buscamos la variabilidad: Somos capaces de reemplazar piezas de nuestra arquitectura sin aparentemente un costo muy grande Buscamos el testing: Somos capaces de testear nuestro código de una forma rápida, sencilla y eficaz. Buscamos la simplicidad: Somos capaces de tener un código simétrico, que sea fácil de entender. Si entiende un caso de uso, serás capaz de entender cualquier otro, nuestro código se vuelve predecible. Esto también nos aleja de errores que no queremos cometer:\nEvitar la complejidad accidental: Evitamos la complejidad accidental al no introducir con nuestros desarrollos más complejidad de la que el sistema ya tiene La estructura de directorios en arquitectura hexagonal Las capas superiores conocen las capas inferiores y no al revés:\nNuestro dominio no conoce detalles de implementación de la capa de infraestructura, solo define el contrato para que sea la infraestructura quien implemente dicho funcionamiento.\nLa aplicación conoce el dominio a modo de poder presentar la información para que otros la consuman, en este caso, sera la capa de infraestructura quien use la aplicación para realizar las operaciones pertinentes.\nLa infraestructura son detalles de implementación, como puede ser una base de datos, y debería poder cambiarse un tipo de infraestructura por otra sin afectar al funcionamiento base de la aplicación y el dominio.\nApplication service vs Domain service Servicios de aplicación Como podemos ver en la imagen, los servicios de aplicación son el punto de entrada de nuestra aplicación. Desde el controlador ya sea de tipo API o línea de comandos, se llama al servición de aplicación para que este se encarge de realizar las operaciones pertinentes, como pueda ser:\nSolicita operaciones al sistema de persistencia. Dichas operaciones comienzan y finalizan en los servicios de aplicación. Publicar eventos de dominio Son el inicio de un caso de uso de nuestro aplicación.\nUn Servicio de aplicación instancia un Servicio de dominio para evitar la duplicidad de código.\nServicios de dominio Son el resultado de agrupar lógica de negocio que podremos reutilizar desde los servicios de aplicación. Imaginemos que tenemos dos casos de uso en nuestra aplicación:\nObtener una playlist en base a su identificador Modificar el nombre de una playlist ¿Que comparten ambos casos?\nNecesitan ir al repositorio de playlists a buscar la playlist dado un identificador Lanzar una excepción de dominio tipo PlaylistNotFound en el caso de que no encuentre la playlist Retornar la playlist en caso de encontrarla Entonces extraeremos a un Servicio de dominio dicho compartimiento común que tendrían nuestros dos casos de uso (dos servicios de aplicación).\nSevicios de infraestructura ❌ No acoplar la estructura de un contrato con su implementación\nUn error muy común sería modelar el dominio de tal forma que aunque no esté acoplado a la infraestructura, si esté pensando para tener la estructura para alguna implementación específica. Por ejemplo, en nuestro aplicación tenemos un servicio de notificaciones, y desde un principio tenemos claro que en la infraestructura inicial estará slack como ese servicio, el error sería modelar el dominio para que cumpla los requisitos que pide tal implementación.\n💉 Inyectar las dependencias de los adaptores/implementaciones por constructor\nPara poder solventar esto, debemos modelar el dominio pensando en que sea lo más abstracto posible, sin influirle con nada, y en el caso de necesitar parámetros específicos para la implementación, lo haríamos mediante su constructor. El contructor nunca lo definiremos en la interface, irá en sus implementaciones, y entonces será la infraestructura la que se encarge de conocer el dominio y de como acoplarse a el.\n🧪 Usamos implementaciones fake de servicios para nuestros test\nUsaremos implementaciones fake para poder testear sin tener que ejecutar el código real que tenemos en la infraestructura. Estaríamos evitando también tener que falsear el comportamiento de un servicio existente, algo que se puede complicar al tratar de mockear las dependencias y funcionamiento de esa implementación.\nTesting en arquitectura hexagonal Testing capa de aplicación y dominio (test unitario) Los test unitarios son los que usaremos para comprobar que la lógica de negocio de nuestros casos de uso (capa de aplicación) y modelos o servicios de dominio se comportan como esperamos. Características principales:\nEl objetivo de estos tests es el de validar que la implementación de nuestra lógica de negocio es correcta. Son los test más rápidos de ejecutar. En estos tests falsearemos la implementación a usar de todo componente de infraestructura. Es decir, allá donde definamos un puerto en nuestros casos de uso, inyectaremos un doble de test para que no hagan operaciones de entrada/salida pero poder validar la interacción del dominio con estos componentes. Importante falsear la interface de dominio y no el cliente final para evitar incurrir en el anti-patrón de Infrastructure Mocking. El test unitario será independiente del punto de entrada. Desde el momento en el que encapsulamos nuestros casos de uso en servicios de aplicación para poderlos reaprovechar desde múltiples puntos de entrada (controlador API HTTP o CLI), el test unitario invocará directamente al caso de uso para desacoplarse también del controlador. Al ser los más rápidos de ejecutar y estar centrados en la lógica de negocio, es en estos test donde ubicamos las comprobaciones más exhaustivas en cuanto a las distintas ramificaciones de nuestros casos de uso. Gracias a que nuestro dominio no conoce detalles de implementación de la capa de infraestructura, solo define el contrato para que sea la infraestructura quien implemente dicho funcionamiento, tenemos la posibilidad de crear implementaciones específicas para nuestros test. Supongamos que tenemos un servicio que necesita un repositorio para persistir en base de datos, dado que no estamos acoplados a una implementación específica, en nuestros test podrías definir una nueva implementación que permita testear solo el comportamiento de la capa de aplicación y de dominio, ya que en este caso no necesitaríamos testear infraestructura en este tipo de test unitario.\nTesting capa de infraestructura Un tipo de test donde el objeto de test es alguna implementación de uno de nuestros puertos. Es decir, en el caso del test unitario, habríamos falseado mediante un doble (PlaylistRepositoryFake) de test la interface de dominio PlaylistRepository, mientras que en el test de integración lo que haremos será justamente testear la implementación de PlaylistRepositoryPgsql para validar que se comporta como esperamos.\n@Test public void it_should_save_a_playlist() { repository().save(\u0026#34;Hello, I am a playlist\u0026#34;); } @Test public function it_should_check_if_exists_a_playlist() { String playlist = \u0026#34;Hello, I am a playlist\u0026#34;; repository().save(playlist); assertThat(repository().search($playlist)).isTrue(); } Test de aceptación Simulan ser un cliente de nuestra aplicación. Entrarán en juego todas las implementaciones reales para comprobar que todo el flujo y la integración con la infraestructura se producen satisfactoriamente. Con lo cuál, las características principales serían:\nEl objetivo de estos tests es el de asegurar que la aplicación funciona correctamente y el flujo completo de las peticiones se puede realizar satisfactoriamente. Son los test más lentos de ejecutar ya que tienen un alcance mayor y sí ejecutan operaciones de entrada/salida como inserts en base de datos ya que usan las implementaciones reales de estos componentes. Aportan mayor valor debido al alcance que tienen (nos asegura que absolutamente todo está ejecutandose como esperamos) En nuestro caso, al implementar una API HTTP, simularemos peticiones HTTP y comprobaremos que las respuestas tienen el código HTTP y el contenido del cuerpo esperados. Al ser los test más lentos de ejecutar, sólo implementaremos una pequeña muestra de las distintas ramificaciones que pueden tomar nuestros casos de uso. Dejando para los test unitarios la responsabilidad de probar cada una de las casuísticas. Así evitaremos incurrir en el anti-patrón de test del cono de helado. ","permalink":"https://raulpadilladelgado.github.io/blog/p/arquitectura-hexagonal/","summary":"\u003ch2 id=\"introducción\"\u003eIntroducción\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eArquitectura de software\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eReglas autoimpuestas al definir como diseñamos software\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e¿Que ganamos entonces imponiéndonos este tipo de reglas?\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eBuscamos la mantenibilidad: Somos capaces de mantener mejor el código gracias a como formamos la arquitectura\u003c/li\u003e\n\u003cli\u003eBuscamos la variabilidad: Somos capaces de reemplazar piezas de nuestra arquitectura sin aparentemente un costo muy grande\u003c/li\u003e\n\u003cli\u003eBuscamos el testing: Somos capaces de testear nuestro código de una forma rápida, sencilla y eficaz.\u003c/li\u003e\n\u003cli\u003eBuscamos la simplicidad: Somos capaces de tener un código simétrico, que sea fácil de entender. Si entiende un caso de uso, serás capaz de entender cualquier otro, nuestro código se vuelve predecible.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eEsto también nos aleja de errores que no queremos cometer:\u003c/p\u003e","title":"Arquitectura hexagonal"},{"content":"¿Por qué aprender bash? La respuesta corta es porque linux es realmente GNU/Linux. Sólo el kernel es linux, pero la colección base de utilidades que proporcionan el entorno Unix es proporcionada por GNU y el shell de GNU es bash. Es por esto que bash el shell por defecto que te encontrarás en cualquier distribución o servidor basado en linux.\nHay muchas shells que se adaptan mejor a los propósitos o gustos individuales, como pueden ser zsh, pero creo que al ser un estandar es muy positivo dominar la herramienta para tener un desarrollo fluido, cómodo y productivo cuando necesites usarla o por si es tu shell de preferencia.\n¿Que dominar de bash? Shortcuts Tab ➡️ autocompletar comandos\nctrl + r ➡️ buscar en el historial de comandos. Pulsa Enter para ejecutar un comando o pulsa flecha derecha para editar\nctrl + w ➡️ borra la última palabra\nctrl + u ➡️ borra hasta inicio de línea\nctrl + k ➡️ borra hasta final de línea\nalt + b ➡️ mover cursor a palabra anterior\nalt + f ➡️ mover cursor a palabra posterior\nctrl + a ➡️ mover cursor a principio de línea\nctrl + e ➡️ mover cursor a final de línea\nctrl + l ➡️ vaciar terminal, limpiarla\ncd ➡️ ir al directorio principal (tu usuario)\n~ == /home/usuario ➡️ abreviación de directorio principal\ncd - ➡️ volver al directorio de trabajo previo\ncd .. ➡️ ir un directorio más arriba\nPuedes encontrar todos los que tienes asignado ejecutando en terminal:\nbind -p Trucos Mi comando es demasiado largo o complejo La solución es sencilla, abrirlo en un editor pero hay una forma muy rápida de realizarlo\nEn primer lugar define tu editor si no lo habías hecho export EDITOR=vim Comienza a escribir un comando echo simpleTest Pulsa ctrl + w y después ctrl + e y automáticamente el comando se abrirá en tu editor Detener procesos Por nombre completo: killall process\nSeleccionando ventana: xkill\nComandos en background La sintaxis es añadir nohup al principio del comando y \u0026amp; al final\nraul@raul-pc:~$ nohup sleep 10 \u0026amp; [1] 4172 raul@raul-pc:~$ nohup: ignoring input and appending output to \u0026#39;nohup.out\u0026#39; ^C raul@raul-pc:~$ ps PID TTY TIME CMD 3615 pts/1 00:00:00 bash 4172 pts/1 00:00:00 sleep 4173 pts/1 00:00:00 ps raul@raul-pc:~$ ps PID TTY TIME CMD 3615 pts/1 00:00:00 bash 4174 pts/1 00:00:00 ps [1]+ Done nohup sleep 10 ¿Cuanto tiempo llevo usando el PC? Utiliza uptime o w que proporciona más informacion que el anterior.\nAbrevia con los alias raul@raul-pc:~$ alias hello=\u0026#34;echo hello world\u0026#34; raul@raul-pc:~$ hello hello world El uso de parentesis Por ejemplo si estamos desarrollando un script y queremos movermos a otro directorio en una sola instrucción y tras esta seguir donde estabamos podemos añadir parentesis a la secuencia que se movia a otro directorio:\n# do something in current dir (cd /some/other/dir \u0026amp;\u0026amp; other-command) # continue in original dir Evitar reproducir un texto raul@raul-pc:~/Desktop$ touch hola.js raul@raul-pc:~/Desktop$ touch hola.html raul@raul-pc:~/Desktop$ mv hola.{js,html} ~/Downloads/ Ver diferencias entre dos archivos diff file1 file2 Redirigir resultados de un comando Guardar salida en archivo\necho test \u0026gt; afile.txt Guardar salida estándar en archivo\necho test 1\u0026gt; afile.txt Guardar error estándar en archivo\necho test 2\u0026gt; afile.txt echo test 2\u0026gt;\u0026amp;1 Ejecutar un comando con los argumentos del anterior raul@raul-pc:~$ touch 4.txt raul@raul-pc:~$ ls !$ ls 4.txt 4.txt raul@raul-pc:~$ Ejecutar scripts al inicio Con systemd\nhttps://juncotic.com/systemd-ejecutando-un-script-al-inicio-de-gnu-linux/\nCon crontab\n#Put into crontab @reboot /home/test/Documentos/scripts/mi-primer-script.sh #Check if crontab is enabled sudo systemctl status cron.service #Enable if is needed sudo systemctl enable cron.service More: https://computernewage.com/2019/03/09/scripting-linux-bash-ejecutar-script-arranque/\nEl PC no se congela del todo Si te pasa que el ordenador se congela al estar usandolo por la ejecucción de alguna aplicación o simplemente porque se ha congelado la sesión de escritorio actual, todavía existe una vía de escape que no es apagar el PC y volver a encenderlo. Si tienes suerte y el PC no se ha congelado del todo, es decir, solo se ha afectado la parte gráfica, puedes seguir estos pasos para restaurar la sesión en poco tiempo:\nEn primer lugar abriremos la terminal que no está asociada a la sesión gráfica, es decir, el clásico Ctrl + Alt + T no nos vale en este caso. En su lugar usaremos Ctrl + Alt + F2 para abrir una terminal, y nos pedirá que introduzcamos el nombre y la contraseña del usuario\nUna vez dentro pasaremos a utilizar el comando htop para acabar con el problema. Si no lo tienes instalado podrás hacerlo de forma normal con APT:\nsudo apt install htop Escribimos htop y se nos abrirá una lista de procesos que están corriendo actualmente en el sistema. Si nuestro problema es una aplicación la localizamos y pulsamos F9 (kill) sobre ella. En el menú de la izquierda aparecen muchas opciones, destacaremos dos:\n15 (Sigterm) ⇒ La mayoría de procesos están escuchando por si el sistema les pide que paren su ejecucción, por lo que debería ser la primera opción que probemos\n09 (Sigkill) ⇒ En el caso de que el anterior opción no nos valga, usaremos esta opción para forzar el cierre del proceso\nUna vez realizado lo anterior, es momento de volver a la interfaz gráfica. Usaremos Ctrl + Alt + F1 para volver a ella.\nReemplazar texto Con el uso del comando sed podemos reemplazar texto sin la necesidad ni el trabajo manual de realizarlo en un editor de texto. Un ejemplo de como usarlo podría ser:\nEste ejemplo muestra el primer carácter de cada palabra en paréntesis:\necho \u0026#34;Bienvenidos al Mundo de los Bits y los Bytes\u0026#34; | sed \u0026#39;s/\\\\(\\\\b[A-Z]\\\\)/\\\\(\\\\1\\\\)/g\u0026#39; Con el resultado:\n(B)ienvenidos(A)l(M)undo de los(B)its y los(B)ytes Si no se lo que hace un comando Tienes un comando con un par de opciones definidas y no sabes que es lo que hace con todas esas opciones, ejemplo: ls -ltrh\nLos comandos sueles traer consigo una opción --help o -helpo -h que te cuentan todo acerca del comando, también puedes recurrir a utilizar otro comando que se llama tldr, que te soltará ejemplos muy usados y con breve descripción para un comando. Pero si aun así necesitas algo más te voy a exponer una solución muy parecida a la opción --help, pero con una interfaz más atractiva y amigable. Es el caso de explainshell.\nexplainshell.com - match command-line arguments to their help text\nTodo esto y mucho más en\u0026hellip; Recientemente me he encontrado un repositorio que contiene una serie de markdowns (mismo contenido, distintos idiomas) que tratan una serie de utilidades para aprender de la terminal, principalmente de Linux, aunque se tratan también aspectos MacOS y Windows. Lo recomiendo mucho porque está explicado de una forma sencilla y porque trata una cantidad de herramientas muy útiles que aprender.\njlevy/the-art-of-command-line\n","permalink":"https://raulpadilladelgado.github.io/blog/p/el-arte-de-la-l%C3%ADnea-de-comandos/","summary":"\u003ch1 id=\"por-qué-aprender-bash\"\u003e¿Por qué aprender bash?\u003c/h1\u003e\n\u003cp\u003eLa respuesta corta es porque linux es realmente GNU/Linux. Sólo el kernel es linux, pero la colección base de utilidades que proporcionan el entorno Unix es proporcionada por GNU y el shell de GNU es bash. Es por esto que bash el shell por defecto que te encontrarás en cualquier distribución o servidor basado en linux.\u003c/p\u003e\n\u003cp\u003eHay muchas shells que se adaptan mejor a los propósitos o gustos individuales, como pueden ser zsh, pero creo que al ser un estandar es muy positivo dominar la herramienta para tener un desarrollo fluido, cómodo y productivo cuando necesites usarla o por si es tu shell de preferencia.\u003c/p\u003e","title":"El arte de la línea de comandos"},{"content":"Cuando me incorporé en el proyecto de uno de nuestros colaboradores, sentía que mis conocimientos estaban limitados y ejercía un rol de principiante, donde mi misión erradicaba en el papel de una esponja, si se me permite la comparación. Debía absorber toda la sabiduría de los compañeros con mayor experiencia laboral. Creo que esta sensación es algo natural, ya que, para cultivar los conceptos necesarios, se debe estudiar y practicar. En esta fase de asimilación de conceptos, cualquier consejo, feedback o enseñanza es de agradecimiento.\nA medida que avanzaban los días, aprendí nuevas tecnologías, metodologías de diseño, formas de hacer test, etc. Era consciente de que obtenía conceptos más técnicos, lo que me aportó confianza para intervenir e intentar ayudar dentro de lo posible. Esta nueva sensación era fundamental para mí, ¿por qué?, es sencillo. Me sentía parte de algo, de un grupo en el que importaba mi presencia y en el que podía ayudar. Esto alimenta un ego positivo que surge en lo más hondo, es una forma de automotivación que anima a dar lo mejor de ti cada día.\nTras medio año, noto un avance de mis conocimientos (a pesar de esto, soy consciente de que me queda muchísimo por aprender), pero he reflexionado acerca de una cualidad que todo desarrollador debe tener, y que, personalmente, tuve que desarrollar para ser más eficaz en el ámbito laboral. Con esto hago referencia a la constitución de la personalidad. He hecho pairing con otros compañeros, en muchas ocasiones con personas con amplia experiencia, lo que es considerado técnicamente como un Senior developer. Mis ganas de aportar eran insaciables, por lo que fluían en mi cabeza un sinfín de ideas sobre lo que se iba desarrollando, pero por motivos que desconozco, me cohibía el pensar en la vasta experiencia de la otra persona, lo que impedía que mis ideas se dieran a conocer. Cuando fui consciente del valor que estaba dejando escapar, mi propósito fue firme, debía aportar cada idea que me surgiera, estuviera seguro o no de la certeza de mi pensamiento. Solo existían dos caminos posibles y ambos conducían a la victoria: el primero, podía estar equivocado, pero mis compañeros propondrían una mejor solución y yo interiorizaría la lección; el segundo, mi idea era cierta y estaba proponiendo algo que podía contribuir al desarrollo y al equipo. Muchas veces esa “pregunta necia” puede mostrarte lo que estabas obviando o darte una nueva perspectiva que te ayude a comprender el tema en cuestión.\nEn innumerables ocasiones observé en silencio a compañeros con mayor experiencia en el sector hablando de temas que desconocía y esto me producía pavor, una sensación de carencia de conocimiento sobre la materia. Tras un trabajo continuo, analizando todo lo que comentan, eres capaz de aprender valiosas lecciones, no sólo aspectos técnicos, sino el valor que se refleja en sus personalidades. A un ingeniero Senior puedes identificarlo más por sus habilidades blandas que por sus técnicas, son personas sumamente pacientes y amigables. Son más unos guías que personas con gran conocimiento técnico. Tienen habilidades extraordinarias de liderazgo y versatilidades sumamente útiles.\nDebe tenerse en cuenta en todo momento que en el equipo en el que te integras quizás no seas la figura con más dominio técnico, pero siempre puedes ser más productivo, quien entrega funcionalidades cuidadas gracias a todo el empeño, buscando las fallas mínimas. Desde mi perspectiva, una de las habilidades más relevantes cuando inicias este recorrido es la paciencia, porque sin ella resulta complicado aprender, puede llegarse incluso a la frustración, sensación por la que las ganas escasean. Siempre existe un momento para ti, pero para lograrlo se debe buscar y trabajar con constancia. Otra habilidad es la perseverancia, porque sin ella no puede dominarse nada, es como aprender a montar en bicicleta, te caes y te levantas hasta que lo consigues.\nDespués de comprender esto, siento que aporto más valor, pero lo que es aún más importante, me siento más libre y capaz de todo. El mejor experto también fue en su día un aprendiz y está en la cumbre porque en su momento falló, acertó, arriesgó, ayudó y aprendió. Si tuviese que extraer una moraleja de esto sería explicando que existen tres tipos de personas en el mundo: los que hacen que las cosas ocurran, los que ven cómo ocurren las cosas y los que se preguntan qué ocurrió (N. Butler).\nAhora mi visión es menos confusa, si hoy no comprendo algo, preguntaré cómo ocurrió y seré yo quien hará que suceda. Se debe aprovechar cualquier momento de aprendizaje, ya que es fundamental para llegar a conseguir una mejor versión de nosotros mismos.\nTodos hemos tenido un mentor, esa persona que es tu referencia y no sólo por su conocimiento, sino por ser una persona capaz de transmitir su sabiduría con los demás de una forma inteligente, con paciencia, respeto y sus mejores intenciones en tu aprendizaje. Un gran compañero quiso transmitirme que podía contar con él para cualquier duda o problema en el trabajo, y me contó unas grandes palabras que nunca olvidaré. Me habló sobre las cuatro etapas de la competencia:\nHaces algo mal y no eres consciente de ello, porque aún no te has dado cuenta Haces algo mal, pero te lo han dicho o te has dado cuenta de alguna forma y eres consciente de ello, por lo que ya estás preparado para mejorar en ese aspecto. Haces algo bien, has aprendido de tus errores y has mejorado, aunque te requiere un esfuerzo y concentración para lograr realizar la tarea correctamente. Haces algo bien, y gracias a tu constancia y perseverancia ya lo realizas sin pensarlo, no necesitas concentrarte, porque dominas lo que haces y para ti es algo cotidiano. En el ciclo vital, recorremos las cuatro etapas. Pasamos de novatos a expertos, es ley de vida, la constancia y el trabajo nos mejora profesionalmente y personalmente. La programación no es una excepción, existen personas con mayor conocimiento que el resto, pero se debe a su trabajo y constancia para lograr dominar el tema en cuestión. Nadie debería negarle su ayuda a otra persona, pues el buen mentor hace que otros con menor experiencia pasen por las cuatro etapas de la competencia de una forma eficiente y provechosa.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/la-personalidad-en-un-aprendiz/","summary":"\u003cp\u003eCuando me incorporé en el proyecto de uno de nuestros colaboradores, sentía que mis conocimientos estaban limitados y ejercía un rol de principiante, donde mi misión erradicaba en el papel de una esponja, si se me permite la comparación. Debía absorber toda la sabiduría de los compañeros con mayor experiencia laboral. Creo que esta sensación es algo natural, ya que, para cultivar los conceptos necesarios, se debe estudiar y practicar. En esta fase de asimilación de conceptos, cualquier consejo, feedback o enseñanza es de agradecimiento.\u003c/p\u003e","title":"La personalidad en un aprendiz"},{"content":"Introducción Durante mucho tiempo fui usuario de Windows, pero un día decidí probar Linux y terminé por elegirlo como mi sistema operativo para trabajar. Depende de cuando leas esto quizá el cambio haya sido al revés. Pero lo dicho, llegando a un mundo nuevo para mí, decidí probar distintas distribuciones Linux para encontrar cuál era la que mejor se adaptaba al uso que le daría. Tras ver un curso en CodelyTV, pude aprender que la configuración tediosa que hacemos día tras día desde que iniciamos en una máquina se puede resumir en la simple ejecución de un script, y puedo decir que ojalá haber conocido está práctica tan simple, pero que aporta y ayuda tanto a la configuración personal.\n¿Qué es un dotfile? Las configuraciones específicas del usuario se almacenan tradicionalmente en los llamados dotfiles (archivos cuyo nombre comienza con un punto). Es una práctica común versionarlos con un sistema de control de versiones como Git para realizar un seguimiento de los cambios y sincronizarlos en varias máquinas. Es decir, gracias a esta práctica podremos pasar a una máquina nueva y configurarla a nuestro gusto, tal y como estaba la anterior de una forma muy rápida y con el mínimo esfuerzo.\nEn este post estaré enseñando como tener nuestro repositorio centralizado de archivos de configuración de nuestra máquina. Veremos todas las cosas que podemos automatizar (que yo he descubierto y entendido por el momento) y entre todos aprenderemos como exprimir esta práctica.\nLa guía Entendiendo el workflow Como he dicho anteriormente, el objetivo es centralizar todas las configuraciones en un repositorio, así que el proceso que debemos realizar ahora es el siguiente:\nIdentifica un archivo o carpeta de configuración que comienza por un punto en nuestra home (directorio raiz de tu usuario) y que queremos guardar ( usa ls -a para listarlas) Mueve una carpeta o archivo desde la home a la carpeta \u0026ldquo;.dotfiles\u0026rdquo; Crea un enlace simbólico para que la configuración pueda ser accedida desde la home pero estando realmente alojada en la subcarpeta \u0026ldquo;.dotfiles\u0026rdquo;( ejemplo: ln -s .dotfiles/.gitconfig $PWD/.gitconfig) Ejemplo\nmv .bashrc .dotfiles ln -s .dotfiles/.bashrc $PWD/.bashrc Resultado .bashrc -\u0026gt; .dotfiles/.bashrc Si necesitas borrar un enlace simbólico porque te has equivocado o la ruta donde lo almacenas ha cambiado, puedes usar el comando unlink : ( Ejemplo) unlink .bashrc\nAhora sí, es momento de automatizar esto 📦 El repo He creado un repositorio en Github llamado \u0026ldquo;dotfiles\u0026rdquo;, que posteriormente clonaré en local en la carpeta \u0026ldquo;.dotfiles\u0026rdquo; que se alojará en mi home.\ngit clone git@github.com:raulpadilladelgado/dotfiles.git .dotfiles 🚫 Exclusión Pongamos el caso de que en una carpeta queremos versionar varias carpetas, pero hay una que concretamente no queremos tener. Usemos el tan querido .gitignore.\nEjemplo:\n.gitignore\nshell/zsh/**/**.zwc shell/zsh/**/**.zwc.old /**/**/private-* 🏗️ Estructura Todo organizado se encuentra mejor y luce mejor. No hay un convenio de carpetas para los dotfiles (aunque puede usar el de CodelyTV) pero puedes aplicar tu habilidad de organización para crear las distintas carpetas para los distintos programas y que quede todo organizado.\n⚙️ Scripts Para symlinks\nAntes estuvimos haciendo uso de los symlinks para nuestro propósito de los dotfiles, pero era un trabajo un muy manual. Para mejorarlo y que solo la tengamos que realizar una vez, podemos hacer un script que ejecutemos cada vez que cambiemos de máquina para poder tener los dotfiles configurados en nuestra máquina de la forma más rápida y sencilla posible.\nPara programas\nYa hemos visto como utilizar los dotfiles para guardar nuestros archivos de configuración y demás, pero podríamos ir un paso más allá, teniendo en nuestro proyecto de dotfiles una forma que nos permita instalar todos nuestros programas en un momento.\nOpción 1 - Importamos una lista de programas que teníamos instalados\nLa idea es poder llevarnos la referencia de los programas que tenemos instalados para después importarlos.\nExportación\nPrimero vamos a sacar la lista de los programas instalados.\nSi tienes MacOS, puedes usar brew para exportar e importar los programas instalados. Dicha utilidad viene instalado por defecto en MacOS.\nbrew bundle dump --file=\u0026#34;$HOMEBREW_BUNDLE_FILE_PATH\u0026#34; --force Si tienes Debian o derivadas, puedes usar apt para exportar los programas instalados.\nsudo dpkg-query -l | awk \u0026#39;{if ($1 == \u0026#34;ii\u0026#34;) print $2}\u0026#39; \u0026gt; packages_list.txt Si usas Windows, puedes usar winget, el cual lleva un tiempo integrado en el sistema asi que si sueles actualizar tu PC deberías tenerlo ya.\nwinget export exported_winget.txt Por último, si quieres guardas lo que has instalado con Pip (gestor de paquetes de Python) o NPM (gestor de paquetes de Node), puedes usar el siguiente comando:\n#Para pip (gestor de paquetes python) pip freeze \u0026gt;\u0026#34;$DOTFILES_PATH/langs/python/requirements.txt\u0026#34; #Para npm (gestor de paquetes de node) ls -1 /usr/local/lib/node_modules | grep -v npm \u0026gt;\u0026#34;$DOTFILES_PATH/langs/js/global_modules.txt\u0026#34; Importación\nAhora que ya tenemos los programas exportados en un archivo, podemos importarlos en nuestra nueva máquina.\nSi has usado brew, puedes usar el siguiente comando:\nbrew bundle --file=\u0026#34;$HOMEBREW_BUNDLE_FILE_PATH\u0026#34; --force Si has usado apt, puedes usar el siguiente comando:\nsudo xargs -a packages_list.txt apt install Si has usado winget, puedes usar el siguiente comando:\nwinget import exported_winget.txt Si has usado pip o npm, puedes usar el siguiente comando:\n#Para pip pip install -r \u0026#34;$DOTFILES_PATH/langs/python/requirements.txt\u0026#34; #Para npm xargs -I_ npm install -g \u0026#34;_\u0026#34; \u0026lt;\u0026#34;$DOTFILES_PATH/langs/js/global_modules.txt\u0026#34; ¿Usas paquetes snap?. Este gestor de paquetes que usan las distribuciones de canonical permite la exportación e importación de los datos de las aplicaciones instaladas. Para ello, sigue los siguientes pasos:\nVacía el directorio (para tener solo la última copia de seguridad de cada programa): /var/lib/snapd/snapshots\nExportación (nos dará un ZIP por programa): snap save\nMueve los archivos a tu proyecto de dotfiles: mv /var/lib/snapd/snapshots .dotfiles/apps/snap\nHaz un symlink a la ruta /var/lib/snapd/snapshots\nInstala los snap en la máquina nueva (puedes hacer un script que te lea la lista): snap install app1 app2 app3\nFinalmente, ejecuta la restauración (el ID es el número por el que comienzan todos los archivos zip, debería ser sencillo porque probablemente todos se te hayan exportado con el mismo ID haciendo referencia a que se exportaron en el mismo proceso): snap restore \u0026lt;id\u0026gt;\nAlgunas apps como spotify llegan a pesar 1 GB que entiendo que puede ser por temas de caché y canciones descargadas, por lo que sería conveniente reducirlo primero antes de añadirlo al proyecto o no incluir algo así.\nOpción 2 - Creamos nuestro propio script de instalación\nAdemás de crear tu propio script, también puedes hacer uso de algunos predeterminados como es Alfred o cualquier otro que encuentres y que te sea útil. Igualmente, la manera de crear el nuestro propio es muy sencilla. Te ejemplifico uno:\n#!/bin/bash #Get ROOT access (only need to specify a password one time) echo \u0026lt;paswword\u0026gt; | sudo -S echo echo \u0026#34;Now you can use sudo without given password\u0026#34; #Install via apt sudo apt install app1 app2 app3 -y #Fix failed installations sudo apt install -f -y #Install via snap sudo snap install app1 app2 app3 Tras tener nuestro script solo nos queda darle permisos de ejecución y ejecutarlo.\nCabe destacar que ambas opciones son compatibles entre sí, por lo que puedes usar ambas a la vez. Si por lo que sea con la primera opción no consigues exportar algún programa, puedes añadirlo en tu segunda opción a tu script de instalación.\nMe quiero llevar las bases de datos que tengo configuradas en IntelliJ\nEn este caso he optado por copiar en un archivo txt las conexiones y también el archivo donde IntelliJ almacena las claves que recuerda para la configuración de una conexión. ¿Cómo lo hice?, muy sencillo:\nPara copiar la configuración de una conexión\nSi tienes varios data sources que quieres guardar, simplemente selecciónalos todos a la vez y elige la misma opción que lo que hará será copiar todos a la vez.\nPara llevarte las contraseñas\nVamos a cambiar como IntelliJ almacena las contraseñas para que lo haga en un archivo que incluyamos en nuestro dotfile. Nos vamos a:\nFile | Settings | Appearance and behavior | System settings | Passwords Y activamos la siguiente opción\nAhora ya podemos añadir a nuestro dotfile el archivo que se muestra en el path.\nBusca ejemplos comunes que se apliquen a ti\nCada uno puede tener sus casos personales en los que tiene que guardar unas cosas u otras, pero llegados a este punto hay muchos que son comunes para usuarios de Linux, MacOS o Windows. Las configuraciones de la shell que uses, la configuración de git, amazon wer services, scripts para agilizar los symlinks, etc. Seguro que por GitHub puedes encontrar muchos ejemplos como el de CodelyTV que te expliqué más arriba. Te puedo ejemplificar como quedó la estructura de uno propio muy sencillo a medida que he ido comprendiendo como trabajar con estos archivos.\nConclusión Con mi propia experiencia te puedo decir que lo mejor es que el proyecto debe vaya evolucionando a tus necesidades haciendo que crezca a medida que lo necesitas, de esta manera tendrás el control y no se volverá un caos. No intentes añadir demasiado ya sea visto en otras plantillas o demás si no lo comprendes o necesitas.\nFinalmente, si te ha gustado o tienes otras ideas, tips, mejoras\u0026hellip; no dudes en proponerlas.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/versiona-tus-dotfiles/","summary":"\u003ch1 id=\"introducción\"\u003eIntroducción\u003c/h1\u003e\n\u003cp\u003eDurante mucho tiempo fui usuario de Windows, pero un día decidí probar Linux y terminé por elegirlo como mi sistema operativo para trabajar. Depende\nde cuando leas esto quizá el cambio haya sido al revés. Pero lo dicho, llegando a un mundo nuevo para mí, decidí probar distintas distribuciones Linux\npara encontrar cuál era la que mejor se adaptaba al uso que le daría. Tras ver un curso en CodelyTV, pude aprender que la configuración tediosa que\nhacemos día tras día desde que iniciamos en una máquina se puede resumir en la simple ejecución de un script, y puedo decir que ojalá haber conocido\nestá práctica tan simple, pero que aporta y ayuda tanto a la configuración personal.\u003c/p\u003e","title":"Versiona tus dotfiles"},{"content":"Introducción Los principio SOLID son convenciones en cuanto a diseño de software que ayudan a conseguir un código más mantenible, tolerante a cambios, y testeable.\nTodos los desarrolladores de un equipo deberían tener nociones de diseño de software para fomentar la autonomía y agilidad del equipo\nHuir de STUPID, el enemigo de SOLID S → Singleton: Hay un objeto que lo contiene todo. No necesita inyección de dependencias. Y se encuentra por todo el programa. Tiene demasiadas resposabilidades.\nT → Tight Coupling: Fuertemente acoplado. Conoces la implementación concreta del repositorio de usuario (un mysql por ejemplo), algo que dificulta el cambio de tipo de base de datos. El código no es tolerante a cambios.\nU → Untestability: Código intesteable. Muy visto en los singleton. Código muy junto sin ningún tipo de inyección de dependencias que nos lleva a tener un código imposible de testear.\nP → Premature Optimization: Realizar mucho más código del necesario atendiendo al futuro. Se debe pensar con vistas a futuro pero a raíz de las posibles necesidades, no de un simple \u0026ldquo;por si acaso\u0026rdquo;.\nI → Indescriptive Naming: Naming confuso que no refleja intencionalidad o significado alguno.\nD → Duplication: Duplicación del mismo código en muchos lados que necesita de una abstracción o extracción a métodos o clases que solo tienen una responsabilidad y pueden ser parte de otra clase.\nUML Connotaciones negativas\nUn modelo de diagramas que tiene una metodologia de trabajo en cascada:\nEspecificación de requisitos → Desarrollo → Testing\nUna forma de trabajo muy lineal que no entiende de cambios durante el ciclo de desarrollo (especificación de requisitos), derivada de como se desarrollaba software hace tiempo.\nVentajas\nLenguaje de diagramas ilustrativo para nuestros diseños de software (clases e interacción entre ellas) Riguroso: Permite especificar hasta un nivel de detalle suficiente para identificar acoplamiento entre clases y sus relaciones sin ser verboso Agnóstico del lenguaje: No entra en detalles de implementación si quiera al nivel de qué lenguaje de programación se está usando\n¿Qué tipos hay?\nCasos de uso: se busca definir todos los posibles casos de uso (acciones) que pueda hacer un usuario\nSecuencia: Se trata de un diagrama que con el que podremos ver el flujo de nuestra aplicación, representando cómo interaccionan las clases (comunicación entre objetos)\nClases: Este tipo de diagramas son muy populares y nos permiten ver no solo los atributos y métodos de cada clase, sino también las diferentes relaciones de herencia, interfaces e implementaciones de estas. ¿Ventajas?:\n→ Diagrama de clases con 4 garabatos para ponernos de acuerdo u obtener feedback de nuestro equipo antes de implementarlo de forma rápida\n→ Documentar implementaciones ya existentes para facilitar la revisión de código. Por ejemplo, a la hora de hacer una nueva Pull Request (PR), generar el diagrama desde IntelliJ/PhpStorm con 2 clics para adjuntar una imagen a la descripción de la PR.\nUML con IntelliJ Con esta herramienta podemos hacer diagramas de clases.\nSeleccionamos las que queremos → Click derecho → Diagrams → Show Diagram\nNos encontraremos unas opciones tal que así:\nCampos Constructor Métodos Propiedades Inner clases También podemos mediante clic derecho sobre una clase abstracta añadir sus implementaciones al esquema:\nOtro truco es añadir las clases usando Espacio\nUn resumen rápido de lo que podemos hacer sobre diagramas en IntelliJ:\nS (Single responsability principle - SRP) ¿Que es?\nUna clase = un concepto = una responsabilidad\nO lo que es lo mismo una sola razón para cambiar\n¿Como?\nClases que funcionen como servicios con pequeños objetivos acotados, entendiéndose un servicio como un orquestador que conecta nuestros modelos con infraestructura (servicios externos).\n¿Por qué?\nBuscamos la alta cohesión entre la conexión entre componentes de nuestro sistema, robustez antes los cambios y evitamos la duplicidad de código, ya que conseguiremos piezas más reutilizables.\n¿Qué tener en cuenta?\nLos nombres → Un nombre muy general como \u0026ldquo;OrderProcessor\u0026rdquo; da lugar a querer reutilizarlo para muchas cosas y acaba teniendo demasiadas funcionalidades, en su lugar busca un nombre más concreto. Cuando respetamos el principio de responsabilidad única, es más fácil introducir modularidad. Entiéndase modularidad como la propiedad que permite subdividir una aplicación en partes más pequeñas (llamadas módulos), cada una de las cuales debe ser tan independiente como sea posible de la aplicación en sí y de las restantes partes.\nOtros ejemplos Todo empieza en el controller\nCon una aplicación basada en una API todo empieza con una petición a un endpoint. Es por ello que tenemos que empezar a cuidar los detalles desde ahí. Un controlador no necesita entender de contruir sentencias SQL, ni mucho menos de interactuar directamente con la base de datos. Un servicio es el encargado de realizar esto de ejecutar lógica de negocio usando infraestructura. Por ello, haz que tu controlador solo reciba llamadas y la redirecione a un servicio dedicado a la causa.\nCuando un servicio y cuando no\nCuando la lógica de negocio no tiene dependencias externas puede ir acoplada al modelo de dominio. Cuando ya existen esas dependencias es mejor tener un servicio externo que se encargue de inyectar por constructor las dependencias que necesite. Esto favorece las testabilidad y la cohesión.\nO (Open-Closed Principle OCP) El software debería estar abierto a extensión y cerrado a modificación\nNos acoplamos a la interfaz, no a la implementación específica, por lo que podremos cambiar en cualquier momento a que objeto \u0026ldquo;medible\u0026rdquo; nos referimos.\nLo mismo podríamos hacer con una clase abstracta que desde un principio defina como se calcula el porcentaje, y sus implementaciones extiendan de alguna manera dicho cálculo.\nUna clase abstracta es útil cuando las implementaciones van a tener una parte común que siempre se repite, como un sistema de bonificaciones que tienen una general y otras específicas. Si esto no ocurre usaremos interfaces, que permiten desacoplar entre capas, el detalle aparecerá en las implementaciones.\nL (Liskov Substitution Principle - LSP) Cualquier clase hija de otra, debería poder reemplazada sin alterar el sistema por otra clase hija de la misma clase\nEjemplo sencillo En este ejemplo el cuadrado extiende del rectángulo, porque a groso modo son lo mismo, pero a la hora de la verdad tienen comportamientos diferentes, por lo que cuando vamos a utilizar los métodos de la clase rectángulo en la clase cuadrado, tenemos que hacer unos apaños que aunque funcionen y el código actúe correctamente, no estamos respetando el LSP, ya que la clase hija (cuadrado) necesita una serie de modificaciones en cuanto a comportamiento para poder extender y así no será posible reemplazar fácilmente un cuadrado por otra figura geométrica que extienda del rectángulo.\nclass Rectangle { private Integer length; private Integer width; Rectangle(Integer length, Integer width) { this.length = length; this.width = width; } void setLength(Integer length) { this.length = length; } void setWidth(Integer width) { this.width = width; } Integer getArea() { return this.length * this.width; } } final class Square extends Rectangle { Square(Integer lengthAndWidth) { super(lengthAndWidth, lengthAndWidth); } @Override public void setLength(Integer length) { super.setLength(length); super.setWidth(length); } @Override public void setWidth(Integer width) { super.setLength(width); super.setWidth(width); } } final class SquareShould { @Test void not_respect_the_liskov_substitution_principle_breaking_the_rectangle_laws_while_modifying_its_length() { Integer squareLengthAndWidth = 2; Square square = new Square(squareLengthAndWidth); Integer newSquareLength = 4; square.setLength(newSquareLength); Integer expectedAreaTakingIntoAccountRectangleLaws = 8; assertNotEquals(expectedAreaTakingIntoAccountRectangleLaws, square.getArea()); } } Que las subclases respeten el contrato definido en la clase padre es justamente lo que nos permite cumplir con este principio para mantener una correctitud funcional\nI (Interface Segregation Principle - ISP) Ningún cliente debería verse forzado a depender de métodos que no usa\nLas interfaces se debe de desarrollar acorde a las necesidades del cliente que las usa, y no de sus implementaciones.\n⛔ Header Interfaces → Una interfaz que ha sido extraida o basada de una clase (por lo que ahora la interfaz es padre de la clase), y que se crea con todos los métodos que dicha clase tenía o necesitaba.\n✅ Role Interface → Una interfaz que ha sido creada a partir de la definición previa de un caso de uso del cliente.\nConseguiremos un código con bajo acoplamiento estructural\nD (Dependency inversion principle - DIP) Módulos de alto nivel no deberían depender de los de bajo nivel. Ambos deberían depender de abstracciones.\nMódulo → Clases\nEj.: Un caso de uso no debe depender de una implementación, sino que debería hacerlo de una abstracción como sería la interfaz.\nEste principio busca mucho la inyección de dependencias, que sería el acto de recibir parámetros en constructor.\nLa finalidad es la substitución de implementaciones y mejorar la testabilidad de las clases.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/principios-solid/","summary":"\u003ch1 id=\"introducción\"\u003eIntroducción\u003c/h1\u003e\n\u003cp\u003eLos principio SOLID son convenciones en cuanto a diseño de software que ayudan a conseguir un código más mantenible, tolerante a cambios, y testeable.\u003c/p\u003e\n\u003cp\u003eTodos los desarrolladores de un equipo deberían tener nociones de diseño de software para fomentar la autonomía y agilidad del equipo\u003c/p\u003e\n\u003ch1 id=\"huir-de-stupid-el-enemigo-de-solid\"\u003eHuir de STUPID, el enemigo de SOLID\u003c/h1\u003e\n\u003cp\u003eS → Singleton: Hay un objeto que lo contiene todo. No necesita inyección de dependencias. Y se encuentra por todo el programa. Tiene demasiadas resposabilidades.\u003c/p\u003e","title":"Principios SOLID"},{"content":"1. JWT Authentication 1.1. ¿Que es JWT? Dicho de forma sencilla, JWT, es una autenticación basada en tokens enviados a las peticiones por cabecera.\nPara más información: https://jwt.io/introduction/\n1.2. ¿Como funciona JWT? Para obtener el token de acceso, el cliente envía una solicitud de inicio de sesión al servidor de autenticación con el nombre de usuario y la contraseña en el cuerpo de la solicitud. El servidor valida el nombre de usuario y la contraseña, luego devuelve un token de acceso. El cliente debe almacenar el token de acceso en algún lugar y debe enviarlo con cada solicitud al servidor en el encabezado de Autorización. Luego, el servidor valida el token de acceso y, si es válido, atiende la solicitud al cliente. 1.3. ¿Como se implementa JWT? ApplicationUser.java En primer definiremos el modelo\npackage com.raulpadilla.domain; import javax.persistence.*; @Entity @Table(name = \u0026#34;credentials\u0026#34;) public class ApplicationUser { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String username; private String password; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } } ApplicationUserRepository.java Definimos un repositorio para el modelo creado anteriormente\npackage com.raulpadilla.infrastructure.repository; import com.raulpadilla.domain.ApplicationUser; import org.springframework.data.jpa.repository.JpaRepository; public interface ApplicationUserRepository extends JpaRepository\u0026lt;ApplicationUser, Integer\u0026gt; { ApplicationUser findByUsername(String username); } ApplicationUserService.java Definimos un servicio con opere con el repositorio\npackage com.raulpadilla.infrastructure.service; import com.raulpadilla.domain.ApplicationUser; import com.raulpadilla.infrastructure.repository.ApplicationUserRepository; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; @Service public class ApplicationUserService { private ApplicationUserRepository applicationUserRepository; private BCryptPasswordEncoder bCryptPasswordEncoder; public ApplicationUserService(ApplicationUserRepository applicationUserRepository, BCryptPasswordEncoder bCryptPasswordEncoder) { this.applicationUserRepository = applicationUserRepository; this.bCryptPasswordEncoder = bCryptPasswordEncoder; } public void save(ApplicationUser applicationUser) { applicationUser.setPassword(bCryptPasswordEncoder.encode(applicationUser.getPassword())); applicationUserRepository.save(applicationUser); } } SecurityConstants.java Una serie de constantes que usaremos más adelante cuando configuremos Spring Security y el posterior login JWT.\npackage com.raulpadilla.infrastructure.security; public class SecurityConstants { public static final String SECRET = \u0026#34;SecretKeyToGenJWTs\u0026#34;; public static final long EXPIRATION_TIME = 864_000_000; // 10 days public static final String TOKEN_PREFIX = \u0026#34;Bearer \u0026#34;; public static final String HEADER_STRING = \u0026#34;Authorization\u0026#34;; public static final String SIGN_UP_URL = \u0026#34;/users/sign-up\u0026#34;; public static final String LOGIN_URL = \u0026#34;/login\u0026#34;; } ApplicationUserDetailsService.java Servicio que implementa que el que usa Spring por defecto con el objetivo de definir como se debe comportar el método que tras verificar que el usuario existe lo pone en el contexto que conoce Spring para saber que el usuario tiene una sesión iniciada.\npackage com.raulpadilla.infrastructure.service; import com.raulpadilla.domain.ApplicationUser; import com.raulpadilla.infrastructure.repository.ApplicationUserRepository; import org.springframework.scheduling.annotation.Async; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import static java.util.Collections.emptyList; @Service public class ApplicationUserDetailsService implements UserDetailsService { private ApplicationUserRepository applicationUserRepository; public ApplicationUserDetailsService(ApplicationUserRepository applicationUserRepository, BCryptPasswordEncoder bCryptPasswordEncoder) { this.applicationUserRepository = applicationUserRepository; } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { ApplicationUser applicationUser = applicationUserRepository.findByUsername(username); if (applicationUser == null) { throw new UsernameNotFoundException(username); } return new User(applicationUser.getUsername(), applicationUser.getPassword(), emptyList()); } } JWTAuthenticationFilter.java El filtro que se encarga de autenticar al usuario tras recibir unas credenciales. El filtro será usado en un archivo de configuración de seguridad que definiremos más adelante.\npackage com.raulpadilla.infrastructure.security; import com.auth0.jwt.JWT; import com.fasterxml.jackson.databind.ObjectMapper; import com.raulpadilla.domain.ApplicationUser; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.User; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import static com.auth0.jwt.algorithms.Algorithm.HMAC512; import static com.raulpadilla.infrastructure.security.SecurityConstants.*; public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; public JWTAuthenticationFilter(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; } @Override public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException { try { ApplicationUser creds = new ObjectMapper() .readValue(req.getInputStream(), ApplicationUser.class); return authenticationManager.authenticate( new UsernamePasswordAuthenticationToken( creds.getUsername(), creds.getPassword(), new ArrayList\u0026lt;\u0026gt;()) ); } catch (IOException e) { throw new RuntimeException(e); } } @Override protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException { String token = JWT.create() .withSubject(((User) auth.getPrincipal()).getUsername()) .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .sign(HMAC512(SECRET.getBytes())); res.addHeader(HEADER_STRING, TOKEN_PREFIX + token); } } JWTAuthorizationFilter.java Cuando el usuario se ha autenticado correctamente es hora de ver si tiene autorización para acceder a determinados recursos, aquí entra en juego este filtro. También se usará en el archivo de configuración definido anteriormente.\npackage com.raulpadilla.infrastructure.security; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import static com.raulpadilla.infrastructure.security.SecurityConstants.HEADER_STRING; import static com.raulpadilla.infrastructure.security.SecurityConstants.SECRET; import static com.raulpadilla.infrastructure.security.SecurityConstants.TOKEN_PREFIX; public class JWTAuthorizationFilter extends BasicAuthenticationFilter { public JWTAuthorizationFilter(AuthenticationManager authManager) { super(authManager); } @Override protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException { String header = req.getHeader(HEADER_STRING); if (header == null || !header.startsWith(TOKEN_PREFIX)) { chain.doFilter(req, res); return; } UsernamePasswordAuthenticationToken authentication = getAuthentication(req); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(req, res); } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { String token = request.getHeader(HEADER_STRING); if (token != null) { // parse the token. String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes())) .build() .verify(token.replace(TOKEN_PREFIX, \u0026#34;\u0026#34;)) .getSubject(); if (user != null) { return new UsernamePasswordAuthenticationToken(user, null, new ArrayList\u0026lt;\u0026gt;()); } return null; } return null; } } WebSecurity.java Finalmente configuramos la seguridad que afecten a nuestras APIs agregándole los filtro creados anteriormente.\npackage com.raulpadilla.infrastructure.security; import com.raulpadilla.infrastructure.service.ApplicationUserDetailsService; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.context.annotation.Bean; import java.util.Arrays; import static com.raulpadilla.infrastructure.security.SecurityConstants.LOGIN_URL; import static com.raulpadilla.infrastructure.security.SecurityConstants.SIGN_UP_URL; @EnableWebSecurity public class WebSecurity extends WebSecurityConfigurerAdapter { private ApplicationUserDetailsService userDetailsService; private BCryptPasswordEncoder bCryptPasswordEncoder; public WebSecurity(ApplicationUserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) { this.userDetailsService = userDetailsService; this.bCryptPasswordEncoder = bCryptPasswordEncoder; } @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and().csrf().disable().authorizeRequests() .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll() .antMatchers(HttpMethod.POST, LOGIN_URL).permitAll() .anyRequest().authenticated() .and() .addFilter(new JWTAuthenticationFilter(authenticationManager())) .addFilter(new JWTAuthorizationFilter(authenticationManager())) // this disables session creation on Spring Security .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder); } @Bean CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList(\u0026#34;*\u0026#34;)); configuration.setAllowedMethods(Arrays.asList(\u0026#34;*\u0026#34;)); configuration.setAllowedHeaders(Arrays.asList(\u0026#34;*\u0026#34;)); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(\u0026#34;/**\u0026#34;, configuration); return source; } } ApplicationUserController.java Para probar que todo funciona correctamente creamos el controlador que interactúe con el servicio de usuarios\npackage com.raulpadilla.application; import com.raulpadilla.domain.ApplicationUser; import com.raulpadilla.infrastructure.service.ApplicationUserService; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; @RestController @RequestMapping(\u0026#34;/users\u0026#34;) public class ApplicationUserController { private final ApplicationUserService applicationUserService; public ApplicationUserController(ApplicationUserService applicationUserService) { this.applicationUserService = applicationUserService; } @PostMapping(\u0026#34;/sign-up\u0026#34;) public void signUp(@RequestBody ApplicationUser applicationUser) throws IOException, InterruptedException { applicationUserService.save(applicationUser); } } Se definió el método para crear un usuario, para hacer login usaremos /login que está controlado por Spring Security y por eso no tenemos que configurarlo nosotros. Dicha request devolverá el token JWT que necesitamos enviar a otras peticiones que hagamos para que podamos ser autenticados y autorizados correctamente.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/jwt-auth-en-spring-boot/","summary":"\u003ch1 id=\"1-jwt-authentication\"\u003e1. JWT Authentication\u003c/h1\u003e\n\u003ch2 id=\"11-que-es-jwt\"\u003e1.1. ¿Que es JWT?\u003c/h2\u003e\n\u003cp\u003eDicho de forma sencilla, JWT, es una autenticación basada en tokens enviados a las peticiones por cabecera.\u003c/p\u003e\n\u003cp\u003ePara más información: \u003ca href=\"https://jwt.io/introduction/\"\u003ehttps://jwt.io/introduction/\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"12-como-funciona-jwt\"\u003e1.2. ¿Como funciona JWT?\u003c/h2\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/blog/p/jwt-auth-en-spring-boot/images/Untitled2.png\"\u003e\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003ePara obtener el token de acceso, el cliente envía una solicitud de inicio de sesión al servidor de autenticación con el nombre de usuario y la contraseña en el cuerpo de la solicitud. El servidor valida el nombre de usuario y la contraseña, luego devuelve un token de acceso.\u003c/li\u003e\n\u003cli\u003eEl cliente debe almacenar el token de acceso en algún lugar y debe enviarlo con cada solicitud al servidor en el encabezado de \u003cem\u003eAutorización\u003c/em\u003e.\u003c/li\u003e\n\u003cli\u003eLuego, el servidor valida el token de acceso y, si es válido, atiende la solicitud al cliente.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"13-como-se-implementa-jwt\"\u003e1.3. ¿Como se implementa JWT?\u003c/h2\u003e\n\u003ch3 id=\"applicationuserjava\"\u003eApplicationUser.java\u003c/h3\u003e\n\u003cp\u003eEn primer definiremos el modelo\u003c/p\u003e","title":"JWT Auth en Spring Boot"},{"content":"Diferencias Scrum: Iteraciones de tiempo fijo (Sprints). La pila del producto (conjunto de tareas) tiene que tener al menos el tamaño de un Sprint. Limita el WIP (WorkInProgress) por iteración. No se permiten cambiar las tareas del Sprint, solo el Sprint. Roles de Scrum Master, de Product Owner y del equipo\nKanban:Trabajo continuo. Se arrastran las nuevas tareas por el panel hasta que lleguen a su estado final. Limita el WIP por el flujo de trabajo. Se puede modificar la tarea hasta que entra en flujo No existen roles.\nUso Kanban y lo uso así Los consejos básicos que he aprendido usando este tipo de workflow:\nEntiende como trabajas y visualizalo Para aumentar el flujo (más cosas en done en menos tiempo) debemos limitar el work in progress. Las cosas se deben terminar por lo que no es necesario que todos trabajemos en algo, si no que todos deberimos trabajar en terminar algo. Menos tarjetas, menos detalle, más enfoque de lo que queremos conseguir, más visualización Se pueden definir clases de servicio para cubrir las necesidades que pueda tener un equipo. Yo he visto algunas para un enfoque concreto debido a la carga de trabajo que acontecía en aquel momento:\nexpedite fixed delivery date standard intangible\nLas épicas son como un contenedor que contienen historias que por si solas no representan nada a la empresa, hasta que no se termina todo lo asociado a la épica no tiene un valor real para el negocio. Una historia que por si sola aporta valor no necesita estar en una épica.\nExisten dos formas de interpretar como se empieza a trabajar según el tablero:\nTrabajar en pull → de derecha a izquierda, de lo más terminado a lo que menos.\nTrabajar en push → al revés, cada persona tiene asociado algo y no intenta terminar cualquier otra cosa que esté más al final.\nComo nuestro objetivo es poder aportar valor a la empresa cuanto antes, la mejor opción es sin duda trabajar en pull.\nConclusión El punto clave para decidir cuando recurrir a uno o a otro pasa por entender que para Scrum el propósito es maximizar el valor entregado, mientras que para Kanban lo importantes es optimizar el flujo de trabajo y aportar valor rápidamente.\nSi por alguna razón no se pueda tener una comunicación efectiva en un equipo, lo mejor sería usar Scrum ya que garantiza el compromiso del equipo y de las personas con las entregas, Si el equipo es capaz de sincronizarse muy bien, no hay ningún problema para usar Kanban y tener un desarrollo fluido.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/scrum-vs-kanban/","summary":"\u003ch1 id=\"diferencias\"\u003eDiferencias\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003eScrum\u003c/strong\u003e: Iteraciones de tiempo fijo (Sprints). La pila del producto (conjunto de tareas) tiene que tener al menos el tamaño de un Sprint. Limita el WIP (WorkInProgress) por iteración. No se permiten cambiar las tareas del Sprint, solo el Sprint. Roles de Scrum Master, de Product Owner y del equipo\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eKanban\u003c/strong\u003e:Trabajo continuo. Se arrastran las nuevas tareas por el panel hasta que lleguen a su estado final. Limita el WIP por el flujo de trabajo. Se puede modificar la tarea hasta que entra en flujo No existen roles.\u003c/p\u003e","title":"Scrum vs Kanban"},{"content":"Recientemente estuve realizando una formación que impartía Carlos Ble. Dicha formación consistía en aprender trucos y consejos para aplicar a la hora de hacer refactor. Lejos de ser solo una charla, los alumnos estuvimos gran parte del tiempo practicando lo que íbamos aprendiendo con cada ejercicio, y ahora yo quiero hablar un poco acerca de ello.\nTras concluir esta formación mi perspectiva acerca del refactor cambió mucho. Generalmente, tendemos a buscar los refactors más complicados, esos que simplifican 20 líneas de código en la mitad o casos por el estilo. Hay entender que el refactor no consiste en hacer el código lo más pequeño posible, porque menos código no es directamente proporcional con código más simple. Estuve viendo muchos ejemplos de que con cambios muy simple como puede ser un cambio de nombre llegas a lograr una mejor semántica en tu código. Los refactor en código legacy deben empezarse por algo sencillo, es decir, desde fuera hacia dentro de un método o una clase.\nNo quiere decir que existen casos para hacer trabajos de refactor más complejos, pero siempre y cuando nos aporten. Al final de este post comparto y repositorio de mi cuenta de GitHub para que podáis que ejemplo estuvimos realizando.\nResumir una formación de este estilo no es posible ya que hay que estar practicando cada concepto y dedicandole un tiempo para reflexionar acerca de ello para poder interiorizarlo, pero si tuviese que destacar los puntos claves que me llevo de esta formación serían los siguientes:\nIDE\nCon herramientas tan avanzadas como IntelliJ los refactors deben ser lo más automáticos posibles. Los refactors del IDE son completamente seguros y terminan ahorrándote tiempo que si lo hicieses de una forma más manual. Más allá de las ventajas de la herramienta, el porqué deberían ser lo más automático posible responde a la garantía de que en ningún momento el código se rompe, que es lo que precisamente un refactor nunca debería hacer. Inline method o Inline variable → Sustituye su valor o funcionamiento en los lugares donde se esté llamando. Muy útil para reemplazar todas las llamadas de un método viejo por uno nuevo. Introduce parameter object → Útil para cambiar un primitivo de un método por un objeto que contiene como propiedad dicho primitivo. Wrap return value → Para cambiar el retorno de una función Introduce funtional parameter → Para declarar un parámetro funcional en la función y ejecutar una cosa o otra dependiendo de dicho parámetro. Encapsulate fields → para cambiar la visibilidad de un campo de la clase, para que uno público se vuelva privado. Se ocupará de generar los getter y setter que le indiquemos. Pull members up → lleva los métodos a la clase padre Use interface where is possible → Usará la clase padre donde sea posible Legacy Code\nUn legacy code puede llegar a ser muy complejo de entender y sobre todo de refactorizar. Lo más importante para éstas situaciones es disponer de una buena pila de test que prueben la funcionalidad, y tras esto si podemos refactorizar con la garantía de que si por error rompemos algo lo sabremos de inmediato con la ejecucción de los test y dejaremos el código a un estado completamente funcional con un control de versiones. Cuando trabajamos con código legacy es muy importante tener cuidado con los cambios que hacemos cuando son a estados de un objeto, asignaciones y condicionales. Son la principal casa de romper un código legacy. Con código legacy es muy útil comprobar la geometría del código. Puedes poner la letra muy pequeña y ver así la vista general del código, o puedes usar extensiones como esta: CodeGlance Otros\nTodo puede ser testeado con un poco de creatividad. ¿Como testear que un método escribe lo correcto en la consola?, podríamos intentar capturar la salida de la consola, pero hay algo mucho más simple para el test como es extender la clase con dicho método y trucar el comportamiento que escribe en consola por almacenarlo en una variable y comprobar así esa variable. Para terminar, me gustaría compartir el repositorio donde estuve trabajando los ejercicios de esta formación. No es posible ver como se hicieron los refactors automáticos, pero sí el resultado final.\nraulpadilladelgado/Refactor-Java-IntelliJ-1\n","permalink":"https://raulpadilladelgado.github.io/blog/p/taller-refactor-java--intellij/","summary":"\u003cp\u003eRecientemente estuve realizando una formación que impartía \u003ca href=\"https://www.carlosble.com/\"\u003eCarlos Ble\u003c/a\u003e. Dicha formación consistía en aprender trucos y consejos para aplicar a la hora de hacer refactor. Lejos de ser solo una charla, los alumnos estuvimos gran parte del tiempo practicando lo que íbamos aprendiendo con cada ejercicio, y ahora yo quiero hablar un poco acerca de ello.\u003c/p\u003e\n\u003cp\u003eTras concluir esta formación mi perspectiva acerca del refactor cambió mucho. Generalmente, tendemos a buscar los refactors más complicados, esos que simplifican 20 líneas de código en la mitad o casos por el estilo. Hay entender que el refactor no consiste en hacer el código lo más pequeño posible, porque menos código no es directamente proporcional con código más simple. Estuve viendo muchos ejemplos de que con cambios muy simple como puede ser un cambio de nombre llegas a lograr una mejor semántica en tu código. Los refactor en código legacy deben empezarse por algo sencillo, es decir, desde fuera hacia dentro de un método o una clase.\u003c/p\u003e","title":"Taller refactor Java + IntelliJ"},{"content":"Interfaces Es una colección de métodos abstractos y propiedades constantes. En las interfaces se especifica qué se debe hacer pero no su implementación. Serán las clases que implementen estas interfaces las que describen la lógica del comportamiento de los métodos. Las clases que hereden de la interfaz solo podrán hacerlo de ella.\nUn momento muy útil en el que declarar una interfaz, puede ser cuando vemos que dos clases tienen el mismo contrato, por ejemplo, tenemos una clase coche y una clase moto, que implementan los mismos métodos de formas distintas.\nSu utilidad se encuentra cuando buscamos muchas implementaciones, por lo que si la idea es solo generar una implementación, deberíamos basarnos en una sola clase. El hecho de estar obligando a pasar primero por la interfaz a la hora de entender el código, se hace tedioso cuando el objetivo es sólo entender el comportamiento de una clase.\nOtro aspecto a cuidar, es el nombre que reciba la interfaz. Incluir en los nombres de interfaces el prefijo \u0026ldquo;I\u0026rdquo; o en el de las implementaciones el sufijo \u0026ldquo;Impl\u0026rdquo;, solo muestra un mal nombre que no describe como funciona el sistema. Se debe buscar nombres que cuenten una historia, por lo que si una interfaz se llama \u0026ldquo;Encryptor\u0026rdquo;, su implementación debería usar ese nombre de base, pero añadiendo que implementa, como puede ser \u0026ldquo;SimpleEncryptor\u0026rdquo;.\ninterface Encryptor{ String Encrypt(String text); } public class SimpleEncryptor implements Encryptor{ @Override public String Encrypt(String text) { return text.toUpperCase(); } } public class ComplexEncryptor implements Encryptor{ @Override public String Encrypt(String text) { return text.toUpperCase().concat(\u0026#34;_SECRET\u0026#34;); } } Clases abstractas Otra opción que tenemos, sería usar una clase abstracta. A diferencia de la interfaz, ésta si define una base sobre la que trabajar las implementaciones, por lo que la idea de usarla es mejorar dicha clase padre, ya sea añadiendo comportamiento o especificando uno nuevo.\nTanto como con las interfaces, como con las clases abstractas los métodos suelen definirse como \u0026ldquo;protected\u0026rdquo; para que solo las implementaciones puedan usarlo.\npublic abstract class Encryptor{ String Encrypt(String text){ return text.toUpperCase(); } } public class EncryptorWithOriginalFunctionality extends Encryptor { @Override public String Encrypt(String text){ String original = super.Encrypt(text); return original.concat(\u0026#34;_SECRET\u0026#34;); } } public class EncryptorWithoutOriginalFunctionality extends Encryptor { @Override public String Encrypt(String text){ return text.concat(\u0026#34;_SECRET\u0026#34;); } } Genéricos Cuando queremos aprovechar un método para que opere con distintos tipos de datos, como puede ser una cadena de texto o un entero, surge la idea de utilizar algo más general como puede la clase Object, ya que dicha ambos tipos de datos son de dicha clase. El problema es que si tenemos una colección que contiene diferentes tipos de datos, no obligamos a usar casteo, algo que a la hora de error no nos detalla mucho sobre el porque ocurre el fallo más allá de no poder realizar un casteo.\npublic class Cache { List\u0026lt;Object\u0026gt; objects = new ArrayList\u0026lt;\u0026gt;(); void addObjects() { objects.add(\u0026#34;a\u0026#34;); objects.add(1); } String getObject() { return (String) objects.get(0); } } @Test void simple_test() { Cache cache = new Cache(); cache.addObjects(); assertThat(cache.getObject()).isEqualTo(\u0026#34;a\u0026#34;); } /*************************************************************************/ public class Cache { List\u0026lt;Object\u0026gt; objects = new ArrayList\u0026lt;\u0026gt;(); void addObjects() { objects.add(\u0026#34;a\u0026#34;); objects.add(1); } String getObject() { return (String) objects.get(1); } } @Test void simple_test() { Cache cache = new Cache(); cache.addObjects(); assertThat(cache.getObject()).isEqualTo(\u0026#34;a\u0026#34;); //no puede realizar el casteo } Otra solución sería decirle a la clase que acepte cualquier tipo de dato, por lo que el programa si ejecutaría en dicho caso, y comprobaríamos el fallo en tiempo de compilación.\npublic class Cache\u0026lt;T\u0026gt; { List\u0026lt;T\u0026gt; objects = new ArrayList\u0026lt;\u0026gt;(); void addObjects(T object) { objects.add(object); } T getObject(int i) { return objects.get(i); } } @Test void simple_test() { Cache cache = new Cache(); cache.addObjects(\u0026#34;a\u0026#34;); assertThat(cache.getObject(0)).isEqualTo(\u0026#34;a\u0026#34;); } /********************************************************************/ public class Cache\u0026lt;T\u0026gt; { List\u0026lt;T\u0026gt; objects = new ArrayList\u0026lt;\u0026gt;(); void addObjects(T object) { objects.add(object); } T getObject(int i) { return objects.get(i); } //no declaramos ningún casteo } @Test void simple_test() { Cache cache = new Cache(); cache.addObjects(1); assertThat(cache.getObject(0)).isEqualTo(\u0026#34;a\u0026#34;); //como acepta el tipo, el error ocurre por no ser lo mismo que esperamos } ","permalink":"https://raulpadilladelgado.github.io/blog/p/clases-interfaces-y-gen%C3%A9ricos/","summary":"\u003ch1 id=\"interfaces\"\u003eInterfaces\u003c/h1\u003e\n\u003cp\u003eEs una colección de métodos abstractos y propiedades constantes. En las interfaces se especifica qué se debe hacer pero no su implementación. Serán las clases que implementen estas interfaces las que describen la lógica del comportamiento de los métodos. Las clases que hereden de la interfaz solo podrán hacerlo de ella.\u003c/p\u003e\n\u003cp\u003eUn momento muy útil en el que declarar una interfaz, puede ser cuando vemos que dos clases tienen el mismo contrato, por ejemplo, tenemos una clase coche y una clase moto, que implementan los mismos métodos de formas distintas.\u003c/p\u003e","title":"Clases, Interfaces y Genéricos"},{"content":"Primitivos y wrappers de primitivos en Java Asignar una variable primitiva usando otra variable primitiva Con los primitivos, cuando asignamos el valor de una variable a el valor de otra variable, simplemente se genera una copia, por lo que la variable original no mutará su estado por más que la variable nueva decida cambiar.\npublic class Main { public static void main(String[] args) { int x = 0; int y = x; y=5; System.out.println(x);//x sigue valiendo 0 } } Funciones que operan primitivos Algo similar ocurre cuando se pasa por parámetro a una función un tipo primitivo, pasa a ser una copia, por lo que es una variable nueva dentro de dicho scope.\npublic class Main { public static void main(String[] args) { int x = 0; Numbers numbers = new Numbers(); numbers.integers(x); System.out.println(x);//x sigue valiendo 0 } } class Numbers{ public static void integers(int number){ number = 5; } } Asignar una variable tipo wrapper primitivo usando otra variable del mismo tipo Con los no primitivos, cuando asignamos el valor de una variable a el valor de otra variable, simplemente se genera una copia, por lo que la variable original no mutará su estado por más que la variable nueva decida cambiar.\npublic class Main { public static void main(String[] args) { Integer x = 0; Integer y = x; y = 10; System.out.println(x);//x sigue valiendo 0 } } Comparando objetos wrappers (que envuelven primitivos) Comparar los primitivos mediante un simple \u0026ldquo;==\u0026rdquo; es seguro, se va a comportar siempre como esperamos.\npublic class Main { public static void main(String[] args) { int x = 0; int y = x; System.out.println(x == y); //el resultado es true } } El problema surge cuando en lugar de usar los primitivos, usamos la clase correspondiente que lo envuelve y que ofrece métodos para operar con él. En el siguiente ejemplo, vemos como diferentes creaciones de un String dan diferentes resultados a la hora de comparar.\npublic class Main { public static void main(String[] args) { String s1 = new String(\u0026#34;campusMVP\u0026#34;); String s2 = new String(\u0026#34;campusMVP\u0026#34;); System.out.println(s1 == s2); //Devuelve false /*----------------------------------------------------*/ String s3 = \u0026#34;campusMVP\u0026#34;; String s4 = new String(\u0026#34;campusMVP\u0026#34;); System.out.println(s3 == s4); //Devuelve false /*----------------------------------------------------*/ String s5 = \u0026#34;campusMVP\u0026#34;; String s6 = \u0026#34;campusMVP\u0026#34;; System.out.println(s5 == s6); //Devuelve true } } La comparación mediante \u0026ldquo;==\u0026rdquo; no siempre funciona como esperamos, por lo que lo mejor sería usar en su lugar \u0026ldquo;equals()\u0026rdquo;.\npublic class Main { public static void main(String[] args) { String s1 = new String(\u0026#34;campusMVP\u0026#34;); String s2 = new String(\u0026#34;campusMVP\u0026#34;); System.out.println(s1.equals(s2)); //Devuelve true /*----------------------------------------------------*/ String s3 = \u0026#34;campusMVP\u0026#34;; String s4 = new String(\u0026#34;campusMVP\u0026#34;); System.out.println(s3.equals(s4)); //Devuelve true /*----------------------------------------------------*/ String s5 = \u0026#34;campusMVP\u0026#34;; String s6 = \u0026#34;campusMVP\u0026#34;; System.out.println(s5.equals(s6)); //Devuelve true } } No solo cuenta que es más seguro realizarlo de ésta forma, además dejas más clara la intención de estar haciendo una comparación por el valor del objeto.\nObjetos propios Funciones que operan nuestros objetos Anteriormente, vimos que los primitivos, o las clases que los usan mejor dicho, ejecutan métodos que devuelven copias modificadas del original. Cuando tenemos nuestros propios objetos la historia cambia. Según como desarrollemos sus métodos, podrá alterar su estado inicial, o simplemente devolver copias tal que lo hacen los primitivos.\nEn el siguiente ejemplo, podemos ver ciertos métodos que cambian el estado original del objeto, porque nuestro propio método en este caso si muta el estado original.\npublic class Main { public static void main(String args[]) { SomeType someType = new SomeType(); SomeType another = new SomeType(); someType.firstMethod(another); // another{ num:5, text:null } System.out.println(another.toString()); } } class SomeType{ public int num; public String text; public void firstMethod(SomeType someType){ someType.num=5; // hace referencia al mismo objeto recibido por parámetro } @Override public String toString(){ return \u0026#34;{num:\u0026#34; + num + \u0026#34;, \u0026#34; + \u0026#34;text:\u0026#34; + text + \u0026#34;}\u0026#34;; } } En cambio podemos provocar que ocurra algo similar que con los primitivos.\npublic class Main { public static void main(String args[]) { SomeType someType = new SomeType(); SomeType another = new SomeType(); someType.firstMethod(another); // another{ num:0, text:null } System.out.println(another.toString()); } } class SomeType{ public int num; public String text; public void firstMethod(SomeType someType){ someType = new SomeType(); // ya NO hace referencia al mismo objeto recibido por parámetro someType.num=5; } @Override public String toString(){ return \u0026#34;{num:\u0026#34; + num + \u0026#34;, \u0026#34; + \u0026#34;text:\u0026#34; + text + \u0026#34;}\u0026#34;; } } Funciones que operan colecciones o arrays de nuestros objetos Otro caso más específico que debemos tener en cuenta, sería el hecho de que los arrays de primitivos, si mutan su valor.\npublic class Main { public static void main(String args[]) { SomeType someType = new SomeType(); SomeType another = new SomeType(); someType.firstMethod(another.num); System.out.println(another.num[0]); // 5 } } class SomeType{ public int [] num = new int[5]; public String text; public void firstMethod(int[] num){ num[0] = 5; } } Tipos de funciones command query separator (cqs) Es un principio que nos dice que debemos de diferenciar entre dos tipos de funciones: por un lado, tenemos las \u0026ldquo;querys\u0026rdquo;, que no alteran el estado del sistema\nc = sum(a,b); *//realiza una \u0026quot;pregunta\u0026quot; y asigna la respuesta a una variable.*\n; por otro lado, tenemos las \u0026ldquo;command\u0026rdquo;, que sí alteran el estado del sistema\nsum(a,b); *//internamente, está asignando el resultado de la operación a algún campo, por lo que sería una \u0026quot;orden\u0026quot;.*\nUna vez conocida la diferencia, cabe recordar evitar la mezcla de éstos dos tipos de funciones, de tal modo que, ni una \u0026ldquo;query\u0026rdquo; debe alterar el estado del sistema, y una \u0026ldquo;command\u0026rdquo; se limita a operar con datos del sistema. Por ejemplo, un value object contiene solo funciones tipo command.\nFunciones puras Son esas funciones que no dependen de información externa para su ejecución. Realizar funciones los mas puras posibles, nos garantiza flexibilidad, además de que esa función puede estar en cualquier sitio, y que por cambiarla no romperemos nada. Aquí vemos un ejemplo, en el que por más que llamemos a la función, si siempre es el mismo parámetro, el resultado será el mismo.\n**class** **SomeType**{ **public** double raizCuadrada(int n){ **return** Math.sqrt(n); }}\n","permalink":"https://raulpadilladelgado.github.io/blog/p/principios-fundamentales-de-los-tipos-de-datos-en-java/","summary":"\u003ch1 id=\"primitivos-y-wrappers-de-primitivos-en-java\"\u003ePrimitivos y wrappers de primitivos en Java\u003c/h1\u003e\n\u003ch2 id=\"asignar-una-variable-primitiva-usando-otra-variable-primitiva\"\u003eAsignar una variable primitiva usando otra variable primitiva\u003c/h2\u003e\n\u003cp\u003eCon los primitivos, cuando asignamos el valor de una variable a el valor de otra variable, simplemente se genera una copia, por lo que la variable original no mutará su estado por más que la variable nueva decida cambiar.\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003epublic class Main {\n    public static void main(String[] args) {\n        int x = 0;\n        int y = x;\n        y=5;\n        System.out.println(x);//x sigue valiendo 0\n    }\n}\n\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"funciones-que-operan-primitivos\"\u003eFunciones que operan primitivos\u003c/h2\u003e\n\u003cp\u003eAlgo similar ocurre cuando se pasa por parámetro a una función un tipo primitivo, pasa a ser una copia, por lo que es una variable nueva dentro de dicho scope.\u003c/p\u003e","title":"Principios fundamentales de los tipos de datos en Java"},{"content":"Implementations Patterns, de Kent Beck, es un libro sobre programación que define buenas prácticas a seguir en el desarrollo de código en Java, con el objetivo de tener un código legible y del que nos sintamos orgullosos. Se busca mejorar la perspectiva que tenga un programador sobre el sistema que va a tratar, para que entienda que cuando escriba código, éste debe hablar por si solo, debe ser la respuesta correcta y simple a una pregunta que se haga una persona cuando debe resolver un problema. Podemos decir entonces que el libro trata la responsabilidad que debe asumir un programador para tener un código satisfactorio.\nLa programación va más allá de la comunicación del hombre con la máquina, el programador debe pensar que su trabajo lo van a ver otras personas que tendrán que interpretar su código, así mismo como puede ser el mismo quien vea su propio trabajo en un futuro. Para lograr que todo ésta interpretación no sea un tormento, deberíamos tener patrones que definan como desarrollamos código simple y eficaz.\nPATRONES Todo programador debería conocer/seguir un conjunto de leyes que de cumplir sus programas como son:\nLa mayor parte del tiempo se dedica a leer, más que a escribir. Nunca hay un \u0026ldquo;terminé\u0026rdquo;. Se invierte más en modificaciones que en el desarrollo inicial. Se estructuran utilizando un conjunto básico de conceptos de flujo de estado y control. Los lectores necesitan entender los programas en detalle y en concepto. Los patrones tienden un puente entre los principios abstractos y la práctica. Además buscan ahorrar tiempo y energía, ya que se establecen para simplificar enormemente una tarea. Nos ayudan a abordar la toma de decisiones en un problema real. Entonces, los patrones de implementación nos ayudan a escribir soluciones razonables para problemas comunes en la programación.\nHay que entender que por muchos problemas que cubramos con patrones no podremos cubrir todas las situaciones que surjan en el desarrollo. Tener una lista de patrones es simplemente una teoría que se adapta a cada situación y necesidad.\nUNA TEORÍA DE LA PROGRAMACIÓN Los valores proporcionan motivación, los principios traducen esa motivación en acción, y finalmente son los patrones los que describen como se va a hacer. El estilo de desarrollo de cada programador viene definido a partir de sus valores personales y de los principios que expresen sus patrones de implementación.\nTres valores fundamentales son la comunicación(un lector puede entenderlo), la simplicidad(eliminar el exceso de complejidad) y la flexibilidad(la forma en que cambian)\nLos principios son ideas más específicas de la programación, y son la base de los patrones, ya que son los que explican el por qué se ha desarrollado determinado patrón.\nLas consecuencias locales definen que si un cambio aquí puede causar un problema allá , entonces el costo del cambio aumenta dramáticamente. El código con consecuencias mayormente locales se comunica de manera efectiva.\nCuando se tiene el mismo código en varios lugares, si se cambia una copia del código hay que decidir si se cambian o no todas las demás copias. Su cambio ya no es local. Cuantas más copias del código, más costará el cambio.\nOtro razonamiento del principio de las consecuencias locales es mantener la lógica y los datos juntos. Poner la lógica y los datos sobre los que opera cerca el uno del otro, en el mismo método si es posible, o en el mismo objeto, o al menos en el mismo paquete.\nLa simetría en el código es donde la misma idea se expresa de la misma manera en todos los lugares donde aparece en el código. En el siguiente ejemplo la segunda afirmación es más concreta que las demás, por lo que debemos llevarla al mismo nivel de abstración.\n*/*BEFORE*/* entrada(); cuenta++; */*AFTER*/* entrada(); increment(); Un último principio es poner juntos la lógica o los datos que cambian a la misma velocidad y separar la lógica o los datos que cambian a velocidades diferentes. Estas tasas de cambio son una forma de simetría temporal.\nMOTIVATION El mantenimiento es caro porque entender el código existente lleva tiempo y es propenso a errores. Hacer cambios es generalmente fácil cuando se sabe lo que hay que cambiar. Aprender lo que hace el código actual es la parte más costosa. Una vez que los cambios se hacen, necesitan ser probados y desplegados.\nPara intentar reducir el costo general debemos encontrar la forma de obtener beneficios inmediatos al tiempo que se establece un código limpio para facilitar el desarrollo futuro, reduciendo así los gastos de mantenimiento.\nEs por ésto que podemos decir que es importante tener patrones de implementación que nos permitan realizar todo lo anterior de una manera rápida, que lo hagamos casi de forma automática.\nCLASES Los patrones de clase tienen mayor alcance que cualquier otro patrón de implementación. Los patrones de diseño nos hablan de las relaciones entre clases.\nUsamos clases para agrupar una serie de datos y asociar una lógica a ellos. En una clase, la lógica debe cambiar de forma más lenta que como lo hacen los datos sobre los que opera. Dichos datos cambian a velocidades similares y son operados por la lógica relacionada. Una programación efectiva con objetos comprende saber agrupar la lógica en clases y representar sus variaciones en función de los datos que usa. Otro aspecto a tener en cuenta es la herencia, para poder definir múltiples variaciones de una clase padre en varias subclases.\nAunque usar clases presente grandes beneficios para la estructuración en nuestro código, debemos saber cuando usarlas y cuando no. Es decir, debemos reducir el número de clases para lograr reducir la dimensión del sistema, pero siempre y cuando se respete que las demás clases no se sobrecargan a raíz de realizar dicha reducción.\n¿Como podemos comunicar nuestras intecCiones correctamente declarando las clases?. A continuación se da una serie de principios que son la respuesta a ello:\nNombre simple de la superclase/interfaz: Un nombre correcto puede llegar a simplificar y mejorar mucho la situación. \u0026ldquo;Las clases son el el anclaje central del diseño\u0026rdquo;, lo que quiere decir que cuando creemos método lo haremos en función del nombre de la clase, por lo que ésta primera definición es crucial para las definiciones que le prosiguen. A veces necesitas seguir adelante con nuevas funciones, tiempo de confianza, frustración y tu subconsciente para proporcionar un nombre mejor.La conversación es una herramienta que me ayuda constantemente a encontrar mejores nombres. Explicar el propósito de un objeto a otra persona te lleva a encontrar mejores nombres para lo que estás describiendo. Nombre de la subclase: Los nombres de las subclases tienen dos trabajos. Necesitan comunicar cómo son de clase y en qué se diferencian. Una vez más, el equilibrio que se debe lograr es entre la longitud y la expresividad. Use esa superclase como base para el nombre de la subclase. Los nombres de clase que son demasiado cortos gravan la memoria a corto plazo del lector. Los grupos de clases cuyos nombres no se relacionan entre sí serán difíciles de comprender y recordar. Usen los nombres de las clases para contar la historia de su código. Interfaz abstracta: Se busca codificar a las interfaces, no a las implementaciones. Esta es otra forma de sugerir que una decisión de diseño no debe ser visible en más lugares de los necesarios. Si la mayor parte de mi código sólo sabe que estoy tratando con una colección soy libre de cambiar la clase concreta más tarde. Puede ser representado en Java como una interfaz o como una superclase. Pague por las interfaces sólo cuando necesite la flexibilidad que ellas crean.Otro factor económico en la introducción de las interfaces es la imprevisibilidad de los programas informáticos. Nuestra industria parece adicta a la idea de que si diseñáramos bien el software no tendríamos que cambiar nuestros sistemas. Interfaz: Una forma de decir \u0026ldquo;Esto es lo que quiero lograr y más allá de eso hay detalles que no deberían preocuparme\u0026rdquo; es declarar una interfaz de Java. Tienen algo de la flexibilidad de la herencia múltiple sin la complejidad y la ambigüedad. Las interfaces como clases sin implementaciones deben ser nombradas como si fueran clases. Por ejemplo, para dejar constancia de una interfaz, podríamos declarar la interfaz como \u0026ldquo;IFile\u0026rdquo;, y la clase que la implementa \u0026ldquo;File\u0026rdquo;. Abtenerse de nombres como \u0026ldquo;FileImpl\u0026rdquo;, ya que es una abreviatura. Clase abstracta(superclase): La otra forma de expresar la distinción entre la interfaz abstracta y la implementación concreta en Java es usar una superclase. La superclase es abstracta en el sentido de que puede ser reemplazado en tiempo de ejecución con cualquier subclase. Las interfaces abstractas necesitan soportar dos tipos de cambio: cambio en la implementación y cambio de la propia interfaz. Las interfaces de Java no soportan bien esta última. Cada cambio en una interfaz requiere cambios en todas las implementaciones. Las clases abstractas no sufren esta limitación. Siempre que se pueda especificar una implementación por defecto, se pueden añadir nuevas operaciones a una clase abstracta sin interrumpir a los implementadores existentes. Una limitación de las clases abstractas (superclase) es que los implementadores sólo pueden declarar su lealtad a una superclase. Si son necesarias otras vistas de la misma clase, deben ser implementadas por interfaces Java. Interfaz versionada ¿Qué haces cuando necesitas cambiar una interfaz pero no puedes? Típicamente esto sucede cuando quieres añadir operaciones. Ya que añadir una operación romperá todos los implementos existentes, no puedes hacer eso. Sin embargo, puedes declarar una nueva interfaz que amplíe la interfaz original y añadir la operación allí. Los usuarios que desean la nueva funcionalidad utilizan la interfaz ampliada mientras que los usuarios existentes permanecen ajenos a la existencia de la nueva interfaz.\nValue Object Este estilo funcional de computación nunca cambia ningún estado, sólo crea nuevos valores. Cuando se tiene una situación estática (quizás momentáneamente) sobre la que se quiere hacer afirmaciones o sobre la que se quiere hacer preguntas, entonces el value object es apropiado. Cuando la situación cambia con el tiempo,entonces el estado es apropiado.\nSubclase Declarar una subclase es una forma de decir, \u0026ldquo;Estos objetos son como esos excepto\u0026hellip;\u0026rdquo; Si tienes la superclase correcta, crear una subclase puede ser una manera poderosa de programar. Con el método correcto para anular a la superclase, puedes introducir una variante de un cálculo existente con unas pocas líneas de código. Como también tiene desventajas: si descubres que\nalgún conjunto de variaciones no está bien expresado como subclases, tienes que trabajar para desenmarañar el código antes de poder reestructurarlo; segundo, tienes que entender la superclase antes de que puedas entender la subclase; tercero, los cambios en una superclase son arriesgados, ya que las subclases pueden depender de propiedades sutiles de la implementación de la superclase.\nUna clave para lograr subclases útiles es implementar la lógica de la superclase en métodos que hagan un solo trabajo, para facilitar la reutilización o cambio en la subclases.\nESTADO Cuando hablamos de estado, nos referimos a esos valores que forman el estado del programa. Cuando usamos POO, éstos valores son pequeñas piezas encapsuladas en objetos, permitiéndonos así entender mejor que valor hizo cambiar el estado.\nA continuación se definen una serie de patrones que afectan al estado:\nAcceso: Hay dos formas de acceder a los valores: acceso a valores almacenados e invocando cálculos. Acceder a la memoria es como invocar una función que devuelve los valores almacenados actualmente. Invocar una función es como leer un lugar de memoria, cuyo contenido simplemente se calcula y no simplemente se devuelve. Dos tipos de acceso: Acceso Directo → Cuando pasamos un dato concreto, lo cuál aporta una clara expresividad, pero pierde en flexibilidad. Acceso Indirecto → Cuando pasamos una variable y el método se encarga de realizar lo que sea necesario antes de asignar dicho dato, lo cual requiere conocer además el comportamiento de otro factor, pero ganamos en flexibilidad, pudiendo aplicar las operaciones que queramos. Estado común: Muchos cálculos comparten los mismos elementos de datos aunque los valores sean diferentes. Cuando encuentre un cálculo de este tipo, comuníquelo declarando los campos de una clase. Estado variante: Otras veces se deben tener diferentes elementos de datos, ya sea porque una clase necesita varias propiedades, en éste caso se deben declaran los campos intentando agrupar los comunes en un estado común. Variables: Datos almacenados que son variantes con el tiempo. Variables locales → Accesibles desde donde se declaran hasta el final del bloque. Recopilador: recolecta información para su uso posterior. Contador: una especie de recopilador que recolecta la cuenta de otros objetos. Explicativas: Las que su nombre son definidos para guiar al lector. Reutilizador: Las que se definen para ahorrar al entorno de trabajo el calculo de una variable que siempre tienen el mismo valor. Elementos: Las que usamos cuando queremos decir \u0026ldquo;por cada objeto en ésta lista\u0026hellip;\u0026rdquo;. Campo: Los atributos que pertenecen y se declaran a un objeto. Ayudante: El campo definido para usar las referencias de otro objeto. Bandera: Los que definen como se va a usar el objeto, y que si tienen método para alterar su estado además dicen que el uso puede variar a lo largo de la ejecucción. Estrategia: Cuando almacenamos la parte variante que puedan tener los métodos, proporcionando métodos para que cambie. Estado: Son como los estratégicos, pero los de estado van más ligados a la identidad del objeto y su funcionamiento general. Componentes: estos campos contienen propiedades del objeto en cuestión que no buscan ser como los estratégicos. Parámetro: es la forma de comunicar a un objeto con otro si no se tienen un campo del objeto en cuestión declarado. Mediante un método un objeto recibe información de otro objeto o el objeto en si. Recopiladores: son pasados con el fin de ir añadiendo información en él. Opcionales: Como cuando tenemos diferentes constructores que reciben distintos número de parámetros, con el fin de pasar solo los datos que queramos o todos. Argumentos variables: Para poder pasar parámetros sin un número determinado de ellos, ya sea mediante una colección, o delegando a la función que cree una colección a partir de todo lo que se le pasa (Class\u0026hellip; classes). Objeto: cuando se pasa un objeto como parámetro con el fin de reusar código en la lógica. Constantes: Datos que son accedidos en muchos sitios, pero que no cambian. Al declararlo así nos aseguramos que la variable está protegida ya que nunca debería cambiar. Por convención debería escribirse su nombre en mayúsculas. Nombres de acuerdo a un rol: Definir las variables según el rol/función que vayan a cumplir. result → si guarda el objeto que retorna una función for(Person person: people) → llamamos a la variable \u0026ldquo;person\u0026rdquo; porque la lista \u0026ldquo;people\u0026rdquo; tiene un conjunto de \u0026ldquo;person\u0026rdquo;. count → guarda la cuenta de algo. etc\u0026hellip; COMPORTAMIENTO Se describen una serie de patrones sobre como expresar el comportamiento de un programa:\nFlujo de control: Expresa los cálculos como una secuencia de pasos. Java es un miembro de la familia de lenguajes en los que la secuencia de control es un principio organizativo fundamental. Las declaraciones adyacentes se ejecutan una detrás de otra. Los condicionales, bucles, excepciones, etc, definen el flujo a seguir del programa. Teniendo ésto claro podemos decir que cada paso cuenta a la hora de tener un buen producto. Flujo principal: Los programadores generalmente tienen en mente un flujo principal de control para sus programas. El procesamiento comienza aquí y termina allí. Puede haber decisiones y excepciones a lo largo el camino, pero el cómputo tiene un camino a seguir. Pensar desde donde y hacía donde queremos llegar, sumará a la hora de tener una mejor idea de como implementar funcionalidad. Mensaje: La programación orientada a objetos enriquece el mensaje que transmitimos, ya que para una secuencia de instrucciones se define un mensaje (método) que abstrae al lector de conocer todos los detalles del funcionamiento, solo necesita aplicar la lógica común. No solo importa a la hora de leerlo, si no que será un factor fundamental a la hora de poder ampliar el comportamiento actual del programa. Eligiendo el mensaje: Debemos tener cuidado con el nombre que elijamos para el mensaje que queremos transmitir, ya que un mal nombre hará una mala representación de la lógica que estamos abstrayendo. Nombres que sean el resumen o el factor clave de lo que hace la lógica. Mensaje descompuesto: Cuando se tiene una serie de algoritmos complicados, podemos usar un nombre que descomponga cada tarea que cumpla el algoritmo y concatene cada una de ellas para formar un nombre descriptivo que permita al lector ignorar los detalles de implementación. 6. Mensaje de reversión: No solo importa que sea un buen mensaje, además debería estar al mismo nivel de abstración que los demás mensajes de su ámbito, por lo que si nuestro mensaje es más complicado que otros mensaje que le proceden o preceden, deberíamos buscar la forma de que queden al mismo nivel abstración.\nvoid compute() { input(); helper.process(**this**); output();} */******************************************/* compute() { **new** Helper(**this**).compute(); } Helper.compute() { input(); process(); output(); } Mensaje de invitación: Cuando queremos transmitir el mensaje de que una clase es solo una idea de funcionamiento y que requiere de especificaciones o subclases, indicamos que la clase es abstracta, por lo que no se puede instanciar, solo mejorar. Mensaje explicativo: Transmitir la intención de la lógica en el mensaje mediante un comentario o el nombre de un método. Flujo excepcional: Si es cierto que un programa siempre tienen un flujo principal, son las clausulas guardas o las excepciones las que crear un flujo alternativo. Clausula guarda: Son condiciones que harán que nuestro método de un salto, ignorando la lógica que venga después, ya que con ésto damos a entender que se ha dado una condición en la que no necesitamos seguir operando. Excepciones: Son condiciones que expresan que no se puede implementar cierta lógica pensada debido a algún fallo en la computación. Podemos crear nuestras propias excepciones para aportar mayor expresividad al código, y así poder manejar mejor éstas excepciones. Con manejar las excepciones nos referimos a recuperarnos del fallo de tal manera que si un bloque de código no se puede ejecutar, hay otro que tomará el relevo y que coge como contexto el fallo obtenido que provocó la excepción. Además, tener nuestra propia excepción, nos da un mayor feedback de porqué falla el código. MÉTODOS Nuestros programas pueden tener una lógica muy variante, ya que vamos desarrollando un bloque de código por función que el programa final deba tener. Perfectamente, toda la lógica podría contenerse en un solo método, pero para eso usamos métodos, para cumplir una tarea específica, evitando tener que un método cumpla más de una tarea. Los beneficios son claros, la legibilidad obtenida al separar la lógica en métodos es un gran punto a favor, pero además entra en juego la expresividad, ya que así distinguimos partes más importantes o menos importantes.\nLo mejor de los métodos es que se entienden por separado, abstraen al lector de tener que leer otras implementaciones que no son necesarias para entender la tarea que está cumpliendo un método. Evitan la repitición de código, ya que varias llamadas a un método equivale a ejecutar tantas veces un bloque de código sin tener que haberlo escrito más que una vez para crear el método.\nQue nos aporten tantos beneficios no quiere decir que no puedan suponer una desventaja, ya que se debe cuidar su tamaño, nombre y propósito. Si haces demasiados métodos muy pequeños, los lectores tendrán dificultades para seguir su fragmentada expresión de ideas. Pocos métodos conducen a la duplicación y a la consiguiente pérdida de flexibilidad.\nA continuación se definen una serie de patrones relacionados con los métodos:\nMÉTODOS COMPUESTOS Cuando se componen métodos a partir de llamadas a otros métodos, cada uno debe tener el mismo nivel de abstracción.\nvoid compute() { input(); flags|= 0x0080; /*FIX: flags();*/ output(); } Una objeción al uso de muchos pequeños métodos es la penalización por rendimiento impuesta por la invocación de todos esos métodos.\nSe suele recomendar desarrollar métodos estableciendo un límite de líneas de código que ronde entre 5 y 15. Aunque hay que tener en cuenta que no se debe sacrificar la legibilidad o expresividad por la longitud estándar de los métodos, ya que por ejemplo un simple espacio en blanco puede llegar a aportar mucho si separa dos estructuras de distinas complejididades, nos aporta expresividad. Un método se convierte en un obstáculo cuando me dedico a tratar de entender el código en detalle.\nEl truco para elaborar un método está en reconocer cuando tengo conjuntos de detalles relativamente independientes que pueden ser movidos a métodos de apoyo.\nREVELAR LA INTENCIÓN EN EL NOMBRE DEL MÉTODO Los métodos deben ser nombrados con el propósito de que un posible invocador pueda tener en mente porqué usa ese método. Nombre métodos para que ayuden a contar la historia.\nVISIBILIDAD DEL MÉTODO Los cuatro niveles de visibilidad -public, package, protected, private- cada uno dice algo diferente sobre sus intenciones para un método. Si no necesitamos que otros sitios conozcan un método, la mejor manera de expresarlo es declararlo private. En cuanto a la flexibilidad, debemos tener en cuenta que cuantos más métodos privados tengamos más fácil sera escalar, ya que no existe código más allá del de la propia clase que depende de él, pero si a raiz de ésto dificultamos demasiado que otras clases que lo necesiten puedan acceder al método, lo mejor sería declarar como tipo public, debemos encontrar en balance para cada situación.\nPublic → Accesible en cualquier paquete o clase. Hacer un método público significa que aceptas la responsabilidad de mantenerlo, ya sea dejándolo sin cambios, o arreglando todas las llamadas si cambia. Protected → Sólo las clases que hereden de la superclase, podrán acceder a métodos declarados así. Private → Sólo será accesible en la misma clase o paquete. Nos aporta una flexibilidad, ya que el cambio se realizará solo en éste punto y no tendremos que realizarlo en múltiples sitios como lo haríamos como un método público. Revelar lentamente los métodos, comenzando con la visibilidad más restrictiva que trabajar y revelarlos cuando sea necesario. Si un método ya no necesita ser visible, reducir su visibilidad. Declarar los métodos finales es similar a elegir su visibilidad. Declarar un método final establece que aunque no te importa que la gente use este método, tú no permitirás que nadie lo cambie, nos da la seguridad de que nadie romperá accidentalmente el objeto ya que su valor es invariante. Declarar un método estático lo hace visible incluso si la persona que llama no tiene el acceso a una instancia de la clase. Los métodos estáticos están limitados en el sentido de que no pueden depender de ninguna instancia por lo que no son un buen depósito para la lógica compleja. El buen uso de los métodos estáticos es como un reemplazo para los constructores.\nOBJETO DE MÉTODO Para crear un objeto de método, busque un método largo con muchos parámetros y variables temporales. Tratar de extraer cualquier parte del método resultaría en largas listas de parámetros en submétodos difíciles de nombrar.\nCrear una clase con el nombre del método. Por ejemplo, complexCalculation() se convierte en ComplexCalculator. Crear un campo en la nueva clase para cada parámetro, variable local y campo utilizado en el método. Dale a estos campos los mismos nombres que tienen en elmétodo original. Crear un constructor que tome como parámetros los parámetros del método de la método original y los campos del objeto original utilizados por el método. Copie el método en un nuevo método, calculate(), en la nueva clase. Sustituir el cuerpo del método original por un código que cree una instancia de la nueva clase. Por ejemplo: complexCalculation() { new ComplexCalculator().calculate(); } Si los campos fueron establecidos en el método original, establézcalos después de los retornos del método: complexCalculation() { ComplexCalculator calculator = new ComplexCalculator(); calculator.calculate(); mean = calculator.mean; variance = calculator.variance; } Asegúrate de que el código refactorizado funcione como el antiguo código. El código de la nueva clase es fácil de refactorizar. Puedes extraer métodos y no tener que pasar nunca ningún parámetro porque todos los datos utilizados por el método se almacenan en campos. A menudo, una vez que se empiezan a extraer los métodos se descubre que algunas variables pueden ser degradadas de los campos a los locales.\nMÉTODO ANULADO O SOBRESCRITO Los métodos anulados son una forma clara de expresar una variación. Los métodos declarados abstractos en un superclase son una clara invitación a especializar un cálculo, pero cualquier método no declarado final es un candidato para expresar una variación en un cálculo existente. Los métodos bien compuestos en la superclase proporcionan una multitud de potenciales ganchos en los que puedes colgar tu propio código. Si el código de la superclase está en pequeños trozos cohesivos, entonces serás capaz de anular métodos enteros. Anular un método no es ninguna de las dos cosas. Puedes ejecutar el código de la subclase y el código de la superclase invocando super.method(); para invocar el método del mismo nombre.\nMÉTODO SOBRECARGADO Cuando declaras el mismo método con diferentes tipos de parámetros, dices \u0026ldquo;Aquí hay formatos alternativos para los parámetros de este método\u0026rdquo;. Los métodos sobrecargados alivian al llamante de la responsabilidad de convertir los parámetros si hay varias formas legítimas de pasando los parámetros. Una variante de la sobrecarga es usar el mismo nombre de método con diferentes número de parámetros. El problema con este estilo de sobrecarga es que los lectores que quieran preguntar: \u0026ldquo;¿Qué pasa cuando invoco este método?\u0026rdquo; necesitan leer no sólo el nombre del método sino también la lista de parámetros antes de que saber lo suficiente para averiguar lo que sucede como resultado de la invocación del método. Si la sobrecarga es complicada, los lectores necesitan entender la sutil sobrecarga reglas de resolución para poder determinar estáticamente qué método se invocará para determinados tipos de argumentos. Los métodos sobrecargados deben servir todos para el mismo propósito, con la variación sólo en los tipos de parámetros. Diferentes tipos de retorno para diferentes sobrecarga dos los métodos hacen que la lectura del código sea demasiado difícil. Es mejor encontrar un nuevo nombre para la nueva intención. Darle a los diferentes cálculos diferentes nombres.\nMÉTODO DE RETORNO El tipo de retorno de un método señala primero si el método es un procedimiento que funciona por efecto secundario o una función que devuelve un tipo de objeto particular. El tipo de retorno void permite distinguir entre procedimientos y funciones.A veces tu intención es que el tipo de retorno sea específico, un tipo de objeto concretoo uno de los tipos primitivos. Sin embargo, le gustaría que sus métodos fueran tan lo más ampliamente posible, así que elige el tipo de retorno más abstracto que expresa su intención. Esto conserva la flexibilidad para que puedas cambiar la tipo de retorno concreto en caso de que sea necesario en el futuro. Generalizar el tipo de retorno también puede ser una forma de ocultar detalles de la implementación. Por ejemplo, la devolución de una colección en lugar deunalista puede animar a los usuarios a no asumir que los elementos están en un orden fijo.\nMÉTODO DE COMENTARIO Expresar la mayor cantidad de información posible a través de los nombres y la estructura de el código. Añade comentarios solo para expresar decisiones e información que no es obvia del código.Las pruebas automatizadas pueden comunicar información que no encaja de forma natural en comentarios de método. Automatizado las pruebas tienen muchas ventajas. Escribirlas es un valioso ejercicio de diseño, especialmente cuando se hace antes de la aplicación. Si las pruebas se realizan, son consistentes con el código. Las herramientas de refactorización automatizada pueden ayudar a mantener las pruebas actualizadas a un nivel bajo costo.\nMÉTODO DE AYUDA Grandes métodos convertidos en varios más pequeños, los denominamos \u0026lsquo;helpers\u0026rsquo; son los ayudantes. Su propósito es hacer que los cálculos de mayor complejidad sean más leídos ocultar detalles irrelevantes y darle la oportunidad de expresar su intención a través del nombre del método.Los ayudantes son típicamente declarados private, pasando a protected si la clase está destinada a ser refinada por subclasificación. Son éstos helpers los que llama otro método que si sea accesible para otras clases, o puede que quizas solo sirvan para apoyar a otro método private de la clase.\nMÉTODO PARA PRUEBA CON IMPRESION Son métodos que definimos sin más funcionalidad que mostrar cierta información útil sobre un objeto, como puede ser untoString();para imprimir información sobre propiedades que sirva de debug de la aplicación entre otras cosas.\nCOLECCIONES El comportamiento de colección solía ser implementado proporcionando enlaces en la estructura de datos en sí misma: cada página de un documento tendría enlaces con la anterior y las siguientes páginas. Más recientemente, la moda ha cambiado a usar un objeto separado para la colección que relaciona los elementos. Esto permite la flexibilidad de poner el mismo objeto en varias colecciones diferentes sin modificar el objeto.\nMetáfora Las colecciones mezclan diferentes metáforas. La primera es la de una variable de valor múltiple, una variable que se refiere a una colección es en realidad una variable que se refiere a varios objetos al mismo tiempo, pero dicha variable no se considera un objeto. Como con todas las variables, puede asignar a una variable de valor múltiple (añadir y quitar elementos), recuperar su valor, y enviar los mensajes variables (con el bucle for). La metáfora de la variable multivaluada se rompe en Java porque las colecciones son objetos separados con identidad. La segunda metáfora mezclada en las colecciones es la de los objetos - una colección es un objeto. Puedes recuperar una colección, pasarla alrededor, probarlo para la igualdad, y enviarle mensajes. Así que, así como las colecciones son variables de múltiples valores, también son objetos. Otra metáfora útil es pensar en las colecciones como conjuntos matemáticos. Una colección divide el mundo de los objetos en objetos que están en la colección y los objetos que no lo son. Dos operaciones básicas en los conjuntos matemáticos están encontrando su cardinalidad (el método size() de las colecciones) y probando la inclusión (representada por el método contains()).\nConceptos El primer concepto expresado por las colecciones es su tamaño. Los Arrays (que son colecciones primitivas) tienen un tamaño fijo, establecido cuando se crea el conjunto. La mayoría las colecciones pueden cambiar de tamaño después de ser creadas. Un segundo concepto expresado a través de las colecciones es si el orden de elementos es importante. El orden puede ser el orden en que los elementos se añadieron o puede ser proporcionada por alguna influencia externa como la lexicográfica comparación. Por último, las consideraciones sobre el rendimiento se comunican mediante la elección de colección. Si una búsqueda lineal es lo suficientemente rápida, una colección genérica es lo suficientemente buena. Si la colección crece demasiado grande, será importante poder probar o acceder elementos por una clave, sugiriendo un conjunto o mapa.\nInterfaces La declaración de la interfaz dice al lector sobre la colección: si la colección está en un orden particular, si hay elementos duplicados, y si hay alguna manera de buscar elementos por clave o sólo por iteración. Los tipos de interfaces para colecciones se describen a continuación:\nArrays: Desafortunadamente, no tienen el mismo protocolo que otras colecciones, por lo que es más difícil cambiar de un array a una colección que de un tipo de colección a otro. A diferencia de la mayoría de las colecciones, el tamaño de un conjunto se fija cuando se crea. Los arrays son más eficientes en el tiempo y el espacio que otras colecciones de simples operaciones. El acceso que tienen los arrays (es decir, elementos[i]) es más de diez veces más rápido que el equivalente de ArrayList (elements.get(i)).\nIterable: Declarar una variable Iterable sólo dice que contiene múltiples valores. Iterable es la base para la construcción del bucle en Java 5. Cualquier objeto declarado como Iterable puede ser usado en un bucle. Esto se implementa llamando tranquilamente al método iterator(). Una de las cuestiones que hay que comunicar al utilizar las colecciones es si se espera que los clientes los modifiquen. Desafortunadamente, Iterable y su ayudante, Iterator, no proporcionan ninguna manera de declarar que una colección no debe ser modificada. Una vez que tienes un Iterator, puedes invocar su método remove(), que elimina un elemento del Iterable subyacente.\nCollection: La colección hereda de Iterable, pero añade métodos para añadir, eliminar, buscar y contar los elementos. Declarar una variable o método como una colección deja muchas opciones para una clase de implementación. Dejando la declaración tan vagamente especificada como sea posible, usted mantiene la libertad de cambiar las clases de implementación más tarde sin que el cambio se extienda a través del código.\nList: A la colección, la lista añade la idea de que los elementos están en un orden estable. Un elemento puede ser recuperado proporcionando su índice a la colección. Una secuencia estable es importante cuando los elementos de una colección interactúan entre sí.Set: Un conjunto es una colección que no contiene duplicados (elementos que informarían que son iguales() entre sí).\nSortedSet(conjunto ordenado): SortedSet almacena elementos ordenados pero únicos. A diferencia del orden de una Lista, que está relacionado con el orden en que los elementos fueron añadidos o por índices explícitos pasados a add(int, Object), el ordenamiento en un SortedSet es proporcionado por un Comparador. En ausencia de un orden explícito, se utiliza el \u0026ldquo;orden natural\u0026rdquo; de los elementos. Por ejemplo, los Strings se clasifican en orden lexicográfico.\nMap: La última interfaz de recolección es Map, que es un híbrido de las otras interfaces. El mapa almacena los valores por clave, pero a diferencia de una lista, la clave puede ser cualquier objeto y no sólo un número entero. Las claves de un mapa deben ser únicas, aunque los valores puede contener duplicados. Los elementos de un Mapa no están en ningún orden particular. Debido a que Map no es completamente como cualquiera de las otras interfaces de la colección, se encuentra sola, sin heredar de ninguno de ellos. Los mapas son dos colecciones en el al mismo tiempo; una colección de llaves conectadas a una colección de valores.\nImplementaciones Collection\nLa clase predeterminada es ArrayList, pero ésta puede no darnos el resultado esperado al realizar un contains(object) o un remove(object), ya que en éste tipo se permiten duplicados y solo borrará el primer resultado encontrado. Lo más seguro y eficaz es usar en su lugar HashSet.\nList\nAñade la idea de que los elementos están en un estable orden. Las dos implementaciones de la Lista de uso común son ArrayList y LinkedList. ArrayList es rápido para acceder a los elementos y lento para añadir y quitar elementos, mientras que LinkedList es lento para acceder a los elementos y rápido para añadir y eliminar elementos.\nSet\nHay tres implementaciones principales de Set: HashSet, LinkedHashSet, y TreeSet (querealmente implementa SortedSet). HashSet es el más rápido pero sus elementos no están en orden garantizado. Un LinkedHashSet mantiene los elementos en el orden en que fueronañadió, pero a costa de una penalización extra del 30% de tiempo por añadir y quitarelementos. TreeSet mantiene sus elementos ordenados de acuerdo a un comparadorpero a costa de hacer que la adición y la eliminación de elementos o la prueba de un elemento lleve un tiempo proporcional a n, donde n es el tamaño de la colección.\nMap\nLas implementaciones de Map siguen un patrón similar a las implementaciones deSet. HashMap es el más rápido y simple. LinkedHashMap preserva el orden de los elementos,iterando sobre los elementos en el orden en que fueron insertados. TreeMap (en realidad una implementación de SortedMap) itera sobre las entradas basadas en el orden delclaves.\nClase Collections Búsqueda\nLa operación indexOf() toma un tiempo proporcional al tamaño de la lista. Llama a Collections.binarySearch(list, element) para devolver el índice de un elemento en ellista. Si el elemento no aparece en la lista, se devolverá un número negativo.\nOrdenar\nLas colecciones también proporcionan operaciones para cambiar el orden de los elementos de una lista. Reverse(list) invierte el orden de todos los elementos de la lista. Shuffle(list) coloca los elementos en orden aleatorio. Sort(list) y Sort(list, comparator) coloca los elementos enen orden ascendente.\nDESARROLLO DE FRAMEWORKS En éste capítulo, se habla de como cambian los patrones de implementación, cuando el fin es desarrollar un framework.\nCAMBIAR LOS FRAMEWORKS SIN AFECTAR A LAS APLICACIONES El dilema fundamental en el desarrollo y mantenimiento de los frameworks es que necesitan evolucionar, pero hay un gran costo por romper el código de cliente existente. La actualización del framework perfecta, añade nuevas funciones sin cambiar ninguna de las existentes, aunque éstas actualizaciones compatibles no siempre son posibles.Cuando desarrollamos un framework la mentalidad debe ser totalmente distinta que cuando realizamos código de producto convencional, ya que con una herramienta de éste tipo, se debe tener en cuenta que en algunas ocasiones es preferible un bloque de código más complejo, pero que es mucho más mantenible y mejorable, sin romper código de cliente. A pesar de ésto, la simplicidad siempre debe estar presente, y considerada siempre que sea posible.\nACTUALIZACIONES INCOMPATIBLES Una actualización que se descompone en pasos más pequeños, avisa al cliente de que es lo nuevo que viene y el por qué debe actualizarse a la nueva API. Un ejemplo de esto son los métodos deprecados, funcionan pero avisan de que se espera eliminar en futuras versiones. Los paquetes pueden proporcionar una forma de ofrecer a los clientes un acceso incremental a las actualizaciones. Introduciendo nuevas clases en un nuevo paquete, puedes darles el mismo nombre como las viejas clases. Por ejemplo, si puedo actualizar org.junit.Assert en org.junit.newandimproved.Assert , entonces los clientes sólo tienen que cambiar las declaraciones de importación para usar el nueva clase. Cambiar las importaciones es menos arriesgado e intrusivo que cambiar el código. Otra estrategia incremental es cambiar la API o la implementación, pero no ambas en la misma versión. Ésta versión intermedia, asociaría la nueva interfaz con el viejo código, o la vieja interfaz con el nuevo código, lo que daría más tiempo para afrontar y adaptarse al cambio. IDEs como Eclipse, ofrecen herramientas automatizadas para actualizar el código de cliente, de tal forma que añade archivos y mueve funcionalidad, con el fin de adaptarse a la nueva versión. Puedes reducir el costo de cambiar el código si los clientes pueden cambiar a tu funcionalidad mejorada con una simple operación de búsqueda/reemplazo. El cambio del nombre de un método, será más barato para los clientes si dejas los argumentos en el el mismo orden.\nFORMATEANDO UN CAMBIO COMPATIBLE Lo ideal sería que el código de cliente depende lo menor posible del framework, y cuando esto no sea posible (para eso está el framework), se debe intentar que la funcionalidad de la que dependa no sea propensa a cambios, algo que se consigue mediante reducir el número de detalles visibles y mostrar detalles reveladores que son menos probables de cambiar y entregar funcionalidad útil, mientras se mantiene la libertad de cambiar el diseño.\nCLASE LIBRERIA Un estilo simple y que considera bastante el futuro de la API es la clase de biblioteca. Representan toda su funcionalidad como llamadas de procedimiento con parámetros simples, entonces los clientes están bien aislados de futuros cambios. Cuando liberas un nuevo de su clase de biblioteca sólo necesita asegurarse de que todos los métodos existentes trabajan igual que antes. La nueva funcionalidad se representa como nueva o nuevas variantes de los procedimientos existentes. La clase Colecciones es un ejemplo de una API representada como una clase de biblioteca. Los clientes la utilizan invocando métodos estáticos, no instanciándola. Nuevas versiones de las clases de colección añaden nuevos métodos estáticos, dejando a los existentes funcionalidad sin cambios.\nOBJETOS Asumiendo que vamos a representar nuestro framework como objeto, existe una tarea más dura que equilibra la simplicidad y la complejidad, la flexibilidad y la especificidad, así que el framework debe ser a la vez útil que útil, estable para los clientes y evolutivo para usted. El truco es, en la medida que puedas manejarlo, escribir el framework para que los clientes dependen sólo de detalles que no es probable que cambien.\nESTILO DE USO Los frameworks pueden soportar tres estilos principales de uso: instanciación, configuración,e implementación. Cada estilo ofrece diferentes combinaciones de usabilidad, flexibilidad y estabilidad. También puedes mezclar estos estilos en un solo frameworks para proveer un mejor equilibrio entre la libertad de diseño para los desarrolladores y el poder para los clientes. El estilo más simple de uso es la instanciación. Cuando quiero un socket de servidor escribo:\nnew ServerSocket()\nUna vez instanciado, funciona invocando métodos en él. La instanciación funciona cuando la única forma de variación que los clientes necesitan es la variación de los datos, no la lógica. La configuración es un estilo de uso más complejo y flexible en el que el cliente crea objetos usando el framework, pero les pasa sus propios objetos para ser llamados en tiempos determinados. Un TreeSet , por ejemplo, puede ser llamado con un Comparator para permitir una clasificación arbitraria de los elementos. La configuración es más flexible que la instanciación porque puede acomodar las variaciones en la lógica así como en los datos. Sin embargo, ofrece menos libertad al programador, porque una vez que empiezas a llamar a un objeto del cliente, surge la necesidad de seguir llamando a ese objeto de la misma manera y al mismo tiempo o arriesgarse a romper el código del cliente. Cuando los clientes necesitan más formas de enganchar su propia lógica que las proporcionadas por configuración, entonces puede ofrecer el uso por implementación. En la implementación,los clientes crean sus propias clases que son utilizadas por el framework. Siempre y cuando la clase de cliente extienda de una clase del framework o implemente una interfaz, el cliente es libre de incluir cualquier lógica que le guste. JUnit mezcla los cuatro estilos de uso:\nJUnitCore es una clase de biblioteca con un método de ejecución estática(Class\u0026hellip;) para ejecutar todas las pruebas en todas las clases. JUnitCore es también instancial, con instancias que proporcionan un control más fino sobre la prueba ejecutando y notificando. Las anotaciones @Test , @Before y @After son una forma de configuración donde la prueba los escritores pueden identificar bits de código para ser ejecutados en ciertos momentos. La anotación @RunWith es una forma de implementación, donde los escritores de pruebas que necesitan un comportamiento de prueba no estándar pueden implementar sus propios corredores.\nABSTRACCIÓN Sobre que forma es mejor para implementar el framework, introduce la cuestión de si representar las entidades abstractas como una interfaz o una superclase común. Cada enfoque tiene ventajas y desventajas para los desarrolladores y clientes. Los dos enfoques tampoco son mutuamente excluyentes. Un framework puede ofrecer a los clientes tanto una interfaz como una implementación predeterminada de esa interfaz.\nINTERFAZ La gran ventaja de ofrecer a los clientes una interfaz es que las interfaces registran pocos detalles. Los clientes no pueden usar \u0026ldquo;accidentalmente\u0026rdquo; más del framework de lo previsto. Sin embargo, esta protección tiene un costo. Mientras las interfaces permanezcan sin cambios están bien, pero introducir un nuevo método en una interfaz rompen todas las implementaciones cliente de esa interfaz. Una variación de las interfaces que proporciona cierta flexibilidad adicional a costa de cierta complejidad son las interfaces versionadas. Si se añaden operaciones a un interfaz, rompes el código del cliente. Sin embargo, puedes crear una subinterfaz y poner las nuevas operaciones allí. Los clientes pueden pasar objetos que se ajusten a la nueva donde se espera la antigua interfaz, pero el código existente continúa trabajando como antes.\nSUPERCLASE Las ventajas de este estilo son las inversas a las de las interfaces: las clases pueden especificar más detalles que las interfaces, pero añadir una operación a una superclase no rompe el código existente. A diferencia de las interfaces, con la superclases, las clases de cliente sólo pueden extender una clase del framework. Reducir el máximo el numero de detalles visibles para el cliente, nos garantiza una menor limitación en un cambio de diseño futuro. Los campos en un framework siempre deben ser privados. Si los clientes necesitan acceso a los datos de los campos, facilítenlo a través de getters. Examine cuidadosamente sus métodos y haga públicos sólo los métodos esenciales o, mejor aún, protegido. Seguir estas reglas permite definir una superclase que expone sólo unos pocos detalles más que la interfaz equivalente pero permite los clientes más flexibilidad para engancharse a su propia lógica. La palabra clave abstract te da una forma de comunicarte con los clientes donde ellos se requieren para llenar la lógica. Proporcionar una aplicación razonable por defecto de métodos donde sea posible para los clientes la posibilidad de empezar fácilmente. La palabra clave final cuando se aplica a una clase evita que los clientes creen subclases, reforzando la instanciación o el estilo de configuración del uso del framework. Los marcos que se organizan en varios paquetes necesitan una declaración de visibilidad que diga, \u0026ldquo;Visible dentro del marco pero no a los clientes\u0026rdquo;. Una solución a este problema es separar los paquetes en publico e interno y comunicar la diferencia incluyendo al nombre \u0026ldquo;internal\u0026rdquo; en las rutas de paquetes internos. Los paquetes internos proporcionan un punto intermedio entre revelar y ocultar detalles del marco. Los clientes pueden elegir por sí mismos cuánta responsabilidad quieren aceptar para construir encima de partes potencialmente inestables del framework.\nSIN CREACIÓN La opción más simple y menos poderosa es prohibir a los clientes crear los objetos de la estructura directamente. Los operadores en un método de factoría pueden garantizar que los eventos están bien formados. La limitación de no permitir que los clientes creen instancias de marco es que impide los usos legítimos de las clases.\nMÉTODO ESTÁTICO (FACTORÍA) Añaden cierta complejidad a la creación de objetos para los clientes, pero dejan al desarrollador más libertad para futuros cambios de diseño. Si un cliente creó una lista diciendo ArrayList.create() en lugar de usar un constructor, entonces la clase concreta del objeto devuelto podría ser cambiada sin que afecta al código de cliente. Otra ventaja de las factorías estáticas es que te dan la oportunidad de comunicar claramente a los clientes el significado de las variaciones en la construcción.\nOBJETO DE MÉTODOS ESTÁTICOS (FACTORÍA) También puedes representar la creación de instancias enviando mensajes a una fábrica en lugar de invocar un método estático. Por ejemplo, un CollectionFactory podría proporcionan métodos para crear todos los diferentes tipos de colecciones. Podría ser usado así:\nCollections.factory().createArrayList()\nUn objeto de fábrica proporciona incluso más flexibilidad que una método estático pero es más complejo de leer. Necesitas rastrear la ejecución del código para ver cuando se crean ciertas clases. Mientras la fábrica sólo se acceda globalmente, un objeto de fábrica no proporcionan más flexibilidad que los métodos de fábrica estáticos.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/an%C3%A1lisis-del-libro-implementation-patterns/","summary":"\u003cp\u003eImplementations Patterns, de Kent Beck, es un libro sobre programación que define buenas prácticas a seguir en el desarrollo de código en Java, con el objetivo de tener un código legible y del que nos sintamos orgullosos. Se busca mejorar la perspectiva que tenga un programador sobre el sistema que va a tratar, para que entienda que cuando escriba código, éste debe hablar por si solo, debe ser la respuesta correcta y simple a una pregunta que se haga una persona cuando debe resolver un problema. Podemos decir entonces que el libro trata la responsabilidad que debe asumir un programador para tener un código satisfactorio.\u003c/p\u003e","title":"Análisis del libro \"Implementation patterns\""},{"content":"\nEs un patrón de diseño que nos va a permitir agregar funcionalidad a un objeto existente sin cambiar su estructura. Se busca poder añadir dinámicamente funcionalidad a un Objeto. Esto nos permite no tener que crear sucesivas clases que hereden de la primera incorporando la nueva funcionalidad, sino otras que la implementan y se asocian a la primera.\nUn gran momento para aplicarlo es cuando tenemos una clase que contiene métodos que realizan algo más que lógica de negocio. Dicha lógica no tiene que ver con la intencionalidad del método, pero si es cierto que necesitamos que éste ahí. Buscamos el desacople de funcionalidades que no deberían estar realizando ciertos métodos de una clase, ya que dichos requisitos responden a una forma de implementación que puede variar en el futuro, y el hecho de tenerlo separado en otros métodos que añaden funcionalidad a uno principal, nos permite cambiar fácilmente la funcionalidad, sin tener que realizar el cambio en muchos lugares del código, lo que nos aporta una gran mantenibilidad del código.\nLa forma típica de implementar éste patrón en Java sería de la siguiente forma. Existe una interfaz\n**public** **interface** **Vehicle** {**void** **start**();} , la cual implementa la clase que especifica\n**public** **class** **Car** **implements** Vehicle { **public** CarType carType; **public** **Car**(CarType carType) { **this**.carType = carType; } **public** **void** **start**() { System.out.print(\u0026#34;Arrancando\u0026#34;);} } , y la clase abstracta que define como se añaden los decoradores a la funcionalidad base.\n**public** **abstract** **class** **Decorator** **implements** Vehicle { **private** Car car; **public** **Decorator**(Car car) { **this**.car = car; } **public** **void** **start**() { car.start();} } Trás ésto, todos los decoradores que vayamos a realizar, deben extender de la clase abstracta anterior, y llamar al método base de la clase padre.\npublic class EnergyDecorator extends Decorator { private Car car; public EnergyDecorator(Car car) { super(car); this.car=car; } public void start() { super.start(); if(car.carType==CarType.ELECTRIC){System.out.println(\u0026#34; con energía eléctrica\u0026#34;);} if(car.carType==CarType.WIND){System.out.println(\u0026#34; con energía eólica\u0026#34;);} } } Para crear los objetos tendríamos una factoría,\npublic class CarFactory { public static void startCar(){ new Car(CarType.DEFAULT).start(); } public static void startEnergySpecificCar(CarType carType) { new EnergyDecorator(new Car(carType)).start(); } } y finalmente lo comprobaríamos.\npublic class CarShould { @Test void start_with_default_car() { CarFactory.startCar(); //-\u0026gt; Arrancando } @Test void start_with_electric_car() { CarFactory.startEnergySpecificCar(CarType.ELECTRIC); // -\u0026gt; Arrancando con energía eléctrica } @Test void start_with_wind_car() { CarFactory.startEnergySpecificCar(CarType.WIND); // -\u0026gt; Arrancando con energía eólica } } ","permalink":"https://raulpadilladelgado.github.io/blog/p/patr%C3%B3n-decorator/","summary":"\u003cp\u003e\u003cimg alt=\"https://upload.wikimedia.org/wikipedia/commons/thumb/e/e9/Decorator_UML_class_diagram.svg/400px-Decorator_UML_class_diagram.svg.png\" loading=\"lazy\" src=\"/blog/p/patr%C3%B3n-decorator/images/400px-Decorator_UML_class_diagram.svg.png\"\u003e\u003c/p\u003e\n\u003cp\u003eEs un patrón de diseño que nos va a permitir agregar funcionalidad a un objeto existente sin cambiar su estructura. Se busca poder añadir dinámicamente funcionalidad a un Objeto. Esto nos permite no tener que crear sucesivas clases que hereden de la primera incorporando la nueva funcionalidad, sino otras que la implementan y se asocian a la primera.\u003c/p\u003e\n\u003cp\u003eUn gran momento para aplicarlo es cuando tenemos una clase que contiene métodos que realizan algo más que lógica de negocio. Dicha lógica no tiene que ver con la intencionalidad del método, pero si es cierto que necesitamos que éste ahí. Buscamos el desacople de funcionalidades que no deberían estar realizando ciertos métodos de una clase, ya que dichos requisitos responden a una forma de implementación que puede variar en el futuro, y el hecho de tenerlo separado en otros métodos que añaden funcionalidad a uno principal, nos permite cambiar fácilmente la funcionalidad, sin tener que realizar el cambio en muchos lugares del código, lo que nos aporta una gran mantenibilidad del código.\u003c/p\u003e","title":"Patrón decorator"},{"content":"Imágenes vs Contenedores Para entender claramente ambos conceptos, me ayuda asemejarlos a la programación habitual, entendiendo que las imágenes son como clases y los contenedores como los objetos instanciados de las clases.\nLa imagen contiene la base para crear un contenedor, y éste carga la imagen para empezar a funcionar. Una imagen puede ser cargada en todos los contenedores que queramos, igual que un contenedor puede cargar varias imágenes. Cualquier cambio realizado en sistema de archivos del contenedor no afecta a la imagen, pues la imagen solo se usa para la creación del contenedor.\nDocker descarga las imágenes que invocamos en la consola desde DockerHub.\nGestión de contenedores /*------------------------------------*/ /*Crear contenedor*/ docker run ubuntu /*------------------------------------*/ /*Crear contenedor en modo interactivo (se controla desde la consola actual) (ubuntu:imagen, bash:comando)*/ docker run -ti ubuntu bash /*------------------------------------*/ /*Crear contenedor en segundo plano*/ docker run -ti -d ubuntu bash /*------------------------------------*/ /*Acceder a contenedor ejecutado en segundo plano*/ docker exec -ti nombreDeContenedor|ID /*------------------------------------*/ /*Crear contenedor que se borra al finalizar su ejecución*/ docker run --rm ubuntu bash /*------------------------------------*/ /*Arrancar contenedor*/ docker start nombreDeContenedor|ID /*------------------------------------*/ /*Parar contenedor*/ docker stop nombreDeContenedor|ID /*------------------------------------*/ /*Listar contenedores activos*/ docker ps /*------------------------------------*/ /*Listar contenedores activos e inactivos*/ docker ps -a /*------------------------------------*/ /*Borrar contenedores parados*/ docker container prune -f /*------------------------------------*/ /*Borrar un contenedor*/ docker rm nombreDeContenedor|ID /*------------------------------------*/ Gestión de imágenes Docker Build y Dockerfile El comando docker build es el comando que ejecuta las instrucciones del fichero dockerfile.\nArchivo Dockerfile FROM → Instrucción que inicializa el sistema de ficheros a usar.\nRUN → Ejecuta un comando dentro del sistema de ficheros.\nWORKDIR → Directorio del sistema de ficheros donde se ejecutaran los comandos.\nENV → Establecer variables de entorno.\nEXPOSE → Puerto en el que se expondrá la imagen.\nVOLUME → Especifica un volumen para nuestro contenedor.\nCOPY → Copiar archivo de un directorio en otro.\nENTRYPOINT | CMD → El entrypoint recibe el comando de cmd. cmd serían los argumentos de entrypoint.\nFROM ubuntu:latest RUN apt-get update -y RUN apt-get install -y python-pip python-dev WORKDIR /app ENV DEBUG=True EXPOSE 80 VOLUME /data COPY . /app RUN pip install -r requirements.txt ENTRYPOINT [\u0026#34;python\u0026#34;] CMD [\u0026#34;app.py\u0026#34;] Comandos para imágenes /*------------------------------------*/ /*Listar imagenes*/ docker images /*------------------------------------*/ /*Descargar imagen a local*/ docker pull ubuntu /*------------------------------------*/ /*Borrar todas las imagenes en local*/ docker images prune --all /*------------------------------------*/ /*Borrar una imagen en local*/ docker rmi nombreDeImagen /*------------------------------------*/ /*Borrar imagenes \u0026#34;basura\u0026#34; (no tiene tag o nombre y no esta referenciada por ningún contenedor)*/ docker images prune -f /*------------------------------------*/ /*Taggear una imagen*/ docker tag nombreDeImagen usuario/ubuntu:0.1.0 /*------------------------------------*/ /*Iniciar sesion en DockerHub*/ docker login /*------------------------------------*/ /*Subir imagen a DockerHub*/ docker push nombreDeImagen /*------------------------------------*/ /*Contruir imagen a partir de receta (fichero Dockerfile) (para el directorio actual)*/ docker build . /*------------------------------------*/ /*DOCKERFILE Para crear imagen siguiendo pasos*/ FROM ubuntu RUN apt-get update RUN apt-get install -y ineutils-ping RUN apt-get install -y nettools /*------------------------------------*/ Docker almacenará en caché los resultados de la primera compilación de un Dockerfile, lo que permitirá que las compilaciones posteriores serán súper rápidas. En cada aparición de un comando RUN en el Dockerfile, Docker creará y confirmará una nueva capa en la imagen.\n/******************************************/ /*Comando que se ejecuta al iniciar la imagen en un contenedor*/ CMD echo \u0026#34;hello world\u0026#34; /******************************************/ /*Ejecutar un comando pasado como parametro a la ejucción de un contenedor*/ docker run --rm test echo \u0026#34;goodbye\u0026#34; /******************************************/ ¿Diferencia entre CMD y ENTRYPOINT? Con CMD considera que lo que pasemos no debe formar parte de la construcción, por lo que si otro comando es pasado al contenedor interfiere con lo anterior será sobreescrito, sin embargo con entrypoint se mantendría, ya que pasa a formar parte de la construcción del contenedor.\n/******************************************/ /*Añadir un ENTRYPOINT a un contenedor*/ docke run --rm --entrypoint=\u0026#34;\u0026#34; test bash /******************************************/ /*Información extensa*/ docker container inspect nombreDeContenedor /******************************************/ /*Listar imágenes*/ docker images ls /******************************************/ /*Borrar volumenes*/ docker volume rm nombreDeVolumen /******************************************/ /*Definir una red, nombre, nombre de host para un contenedor*/ docker run --rm --network=none --name nombreDeContenedor --hostname nombreDeHost /******************************************/ /*Crear una red*/ docker network create test /******************************************/ Docker garantiza el aislamiento de contenedores hacía otros contenedores que están en distintas redes.\nDOCKER COMPOSE Compose es una herramienta para definir y ejecutar aplicaciones multicontenedores en Docker. Utiliza un archivo YAML para configurar los servicios de su aplicación. Luego, con un solo comando, crea e inicia todos los servicios desde su configuración.\nDefina el entorno de su aplicación con un Dockerfile para que pueda reproducirse en cualquier lugar. Defina los servicios que componen su aplicación docker-compose.yml para que puedan ejecutarse juntos en un entorno aislado. Ejecutar docker-compose up y compose inicia y ejecuta toda su aplicación. Ejemplo de archivo docker-compose.yml\nversion: \u0026#39;2.0\u0026#39; services: web: build: . ports: - \u0026#34;5000:5000\u0026#34; volumes: - .:/code - logvolume01:/var/log links: - redis redis: image: redis volumes: logvolume01: {} Otros comandos docker-compose config → verificar que nuestro compose está correctamente estructurado\nArrancar servicios con docker compose → docker-compose up docker-compose ls → listar servicios activos\ndocker-compose exec php bash → entrar en servicio activo\ndocker-compose down → Stops containers and removes containers, networks, volumes, and images created by up.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/docker-basics/","summary":"\u003ch1 id=\"imágenes-vs-contenedores\"\u003e\u003cstrong\u003eImágenes vs Contenedores\u003c/strong\u003e\u003c/h1\u003e\n\u003cp\u003ePara entender claramente ambos conceptos, me ayuda asemejarlos a la programación habitual, entendiendo que las imágenes son como clases y los contenedores como los objetos instanciados de las clases.\u003c/p\u003e\n\u003cp\u003eLa imagen contiene la base para crear un contenedor, y éste carga la imagen para empezar a funcionar. Una imagen puede ser cargada en todos los contenedores que queramos, igual que un contenedor puede cargar varias imágenes. Cualquier cambio realizado en sistema de archivos del contenedor no afecta a la imagen, pues la imagen solo se usa para la creación del contenedor.\u003c/p\u003e","title":"Docker basics"},{"content":"\nINTRODUCCIÓN \u0026ldquo;Diseño Ágil con TDD\u0026rdquo;, por Carlos Ble, es un libro muy interesante que nos enseña como implementar Test-Driven Development en el desarrollo de código. Muestra como basar nuestro código en los Test que escribimos, y no al revés. A continuación comparto mis experiencias leyendo éste libro.\n¿QUE BENEFICIOS NOS APORTA? Se presentan grandes beneficios de codificar de ésta forma. Se habla de conseguir un código simple, que haga lo que necesitamos para cada momento, que cuando falle nos de un correcto y constante feedback de porqué eso está ocurriendo, así como un código legible y fácil de mantener. Obliga a los programadores a pensar primero en cual es la solución que quieren conseguir antes de comenzar a implementarla, y como consecuencia llegar a manejar diversas formas de conseguir lo que se desea teniendo en cuenta más casos de uso\nUn programador debería tener siempre el control del código, no tiene porque llevarse sorpresas. Cuando dedicamos tiempo a desarrollar pruebas nos estamos asegurando de que vamos a crear un código que implementa lo que cada caso de prueba requiere. Saber cuando va a fallar y por qué va a fallar nos da un control sobre el código que lo vuelve mantenible y mejorable. Incluso cuando nos esperamos un fallo en la aplicación, una prueba realizada con anterioridad nos puede estar diciendo que está fallando y por qué, por lo que nunca perdemos el sentido de lo que hace nuestra aplicación.\nEl ciclo de crear un test aun sin implementar código para verlo fallar (rojo), desarrollar el código para ese caso de prueba, y finalmente verlo pasar la prueba (verde) nos hace tener ese control de que sabemos lo que están haciendo nuestros métodos en todo momento. Cuando sabemos aplicar ésto y entedemos el porqué añadir pruebas a nuestros proyectos, tenemos un código mantenible, ese que facilita de la vida al programador cuando tiene que arreglar fallos en la aplicación. Por otro lado implementar TDD también comprende los ciclos de refactorización. Tras ver nuestra prueba en verde, sabemos que tenemos un código correcto y funcional, por lo que es el momento perfecto para tratar de transformar el código a uno más simple y legible. No se trata de cambiar la funcionalidad del código, ya que hemos visto que es correcta, se trata de que haga lo mismo pero con una presentación más amigable, que cuando se lea parezca que cuenta una historia fácil de seguir.\nUn punto positivo de aplicar TDD es que vamos implementando sólo que necesitamos en cada momento, por lo que evitamos añadir funcionalidades \u0026ldquo;extra\u0026rdquo; que no son necesarios para el estado actual de la aplicación, una práctica que llegar a complicar mucho el código así como su compresión y el control sobre él.\n¿COMO DEBEMOS TESTEAR? Para nuestros test usamos BDD (Desarrollo guiado por el comportamiento), que nos dice que nuestro test debe seguir el patrón Given-When-Then(Preparación, ejecución y validación):\nGiven → Dado unos elementos\u0026hellip; When → \u0026hellip;realiza ésto\u0026hellip; Then → \u0026hellip;y espero que ocurra lo siguiente. Gracias a BDD, nuestros test tienen una estructura clara, sencilla y concreta, cuentan una historia. Una persona que no sepa programar debería entender que pretende hacer el test, por lo que buscamos el menor numero de líneas posibles en cada bloque. Para lograr que nuestro bloque \u0026lsquo;Then\u0026rsquo; compruebe varios factores pero que aparezca en una sola línea, podemos crear nuestra propia aserción:\n/*Llamada*/ assertThatList(list).isExactly(10, 20) /*Implementación*/ fun assertThatList(list: List\u0026lt;Number\u0026gt;) : ListMatchers { return ListMatchers(list) } class ListMatchers(val actualList: List\u0026lt;Number\u0026gt;) { fun isExactly(vararg items: Number){ assertThat(items.size).isEqualTo(actualList.size) for(i in items.indices){ assertThat(items[i]).isEqualTo(actualList[i]) } } } Los nombres de los test deben tener un nombre significativo que puede formar un juego de palabras con el nombre de la clase para formar una frase con sentido y que aclare la intención del test. Deben ser más abstractos que su contenido, esto es no dar detalle de la implementación.\nTenemos la opción de crear un método decorado con la anotación @Before, que significa que el framework ejecutará ese método justo antes de cada uno de los test. Si hay N test, se ejecutará\nN veces. Está pensado así para garantizar que los test no se afecten unos a los otros.Cuando recurrimos a debug para encontrar un error en nuestro test, estamos perdiendo el control del flujo del programa, por lo que la mejor solución es buscar una solución alternativa a lo que estamos haciendo que si comprendamos y que no de lugar a errores inesperados.En la misión de refactorizar nuestro gran aliado son los IDE, que realizan de forma automática las transformaciones como extraer variables, y que el punto fuerte de todo esto es evitar el error humano. No que es que sea una simple ayuda realizar refactorización automática, es que es lo aconsejable, ya que nos garantiza que un cambio realizado en una clase se efectuará en los demás sitios donde esté referenciado, algo fundamental para romper nuestro código.Por mucha cobertura de código que obtengamos realizando test a nuestras líneas de código, no nos garantiza una efectividad del 100%. Para solventar ésto, el testeo automático se debe complementar con testeo exploratorio, ese tipo de pruebas que realizamos manualmente y que prueban la aplicación por completo como si se tratase del usuario final. Cuando aplicamos TDD no buscamos reeemplazar las pruebas manuales, solo tratamos de tener que hacerlo lo menor posible cuando algo se puede automatizar facilmente para así no perder tiempo y dinero en algo repetitivo.\nIncluso en el testeo exploratorio se pueden automatizar algunas partes. Para lograr ésto tenemos los test basado en propiedades, que no definen datos concretos, si no que en su lugar se definen propiedades o casos que debe cumplir el código que se va a probar, como el caso de que el resultado de una suma debe ser mayor que cualquiera de sus sumandos. Las herramientas que ofrecen este tipo de automatización generan un gran número de combinaciones complejas con el fin de probar todos los posibles caminos llegando a casos extremos en los que un programador no puede estar pensando.\nPRINCIPIOS NECESARIOS La premisa de la prioridad de transformación (TPP) es un factor fundamental para realizar TDD, y nos habla de implementar el código más sencillo para un test que está en rojo, escribir código solo para que dicho test pase, sin generalizar para otros casos. El porqué hacerlo así se debe a que te obliga a ir pensando todos los casos e ir implementando poco a poco lo que necesites, sin tener que añadir más de lo que puedes llegar a necesitar, o de añadir cosas que no comprendes porque todavía no has elaborado un caso de prueba para cierta funcionalidad. Cada test en verde debe producir una generalización en el código para otro caso de prueba nuevo. La clave para entender bien éste concepto es ver que un problema se descompone a su vez en muchos subproblemas que son los que vamos a ir resolviendo poco a poco, desde lo más sencillo, a lo más complicado.\nEl principio de menor sorpresa, nos cuenta que el código debe hacer en todo momento lo que espera que haga, esto es, una función debe hacer lo que intuitivamente se espera al interpretar su nombre, parámetros, etc. Sin tener que entrar a ver como está implementada se debe tener la idea de que es lo que hace. Si una función hace lo que su nombre indica pero además otras cosas por detrás que no van a acorde con la misión de dicha función, es un código que genera sorpresas y que no deseamos tener. Para evitar que una función realice más cosas de las que le corresponden podemos recurrir a la separación de responsabilidades, llevando tal comportamiento a otra función que realice solo eso. Además, es importante tener el mismo nivel de abstracción en toda la función para que ningún bloque parezca mucho más complicado que otro visualmente.\n/*ANTES*/ r.wrap(); s.getText().concat(\u0026#34;! is the best.\u0026#34;); /*DESPUÉS*/ r.wrap(); s.append(\u0026#34;! is the best.\u0026#34;); Para aplicar TPP, nos sirven de ayuda las trasformaciones que propone Robert C. Martin:\n{} –\u0026gt; nil: De no haber código a devolver nulo. nil -\u0026gt; constant: De nulo a devolver un valor literal. constant -\u0026gt; constant+: De un valor literal simple a uno más complejo. constant -\u0026gt; scalar: De un valor literal a una variable. statement -\u0026gt; statements: Añadir más líneas de código sin condicionales. unconditional -\u0026gt; if: Introducir un condicional scalar -\u0026gt; array: De variable simple a colección. array -\u0026gt; container: De colección a contenedor. statement -\u0026gt; recursion: Introducir recursión. if -\u0026gt; while: Convertir condicional en bucle. expression -\u0026gt; function: Reemplazar expresión con llamada a función. variable -\u0026gt; assignment: Mutar el valor de una variable. Algo que puede llegar a resultar chocante para un programador es que le digan que para trabajar correctamente con TDD debe dejar de pensar primero como un programador. ¿Entonces como debe actuar?, es sencillo, anteriormente he explicado que TDD nos hace ir paso a paso en el desarrollo de código para que así podamos pensar al detalle cada posible caso de prueba de la aplicación, entonces podemos decir que el programador que aplica TDD en su modus operandi siempre piensa primero como un analista de negocio que interpreta primero los posibles casos o reglas de negocio, y no como un programador que solo busca codificar cuanto antes la solución sin atender a pensar un poco como llegará hasta ahí.\nPara ilustrar mejor como sería aplicar TDD desde 0 podemos ver un pequeño ejemplo de una función que comprueba si una contraseña dada se considera fuerte o segura.\n/*EL CASO MÁS SIMPLE*/ describe(\u0026#39;The password strength validator\u0026#39;, () =\u0026gt; { it(\u0026#39;considers a password to be strong when all requirements are met\u0026#39;, () =\u0026gt; { expect(isStrongPassword(\u0026#34;1234abcdABCD_\u0026#34;)).toBe(true); }); }); /*Y SU SOLUCIÓN PARA LLEGAR AL VERDE*/ function isStrongPassword(password){ return true; } /******************************************************************************/ /*UN CASO MÁS GENERAL*/ it(\u0026#39;fails when the password is too short\u0026#39;, () =\u0026gt; { expect(isStrongPassword(\u0026#34;1aA_\u0026#34;)).toBe(false); }); /*Y SU SOLUCIÓN PARA LLEGAR AL VERDE*/ function isStrongPassword(password){ return password.length \u0026gt;= 6; } Podemos ver como primero se contempló el caso más obvio el más simple, que no es otro que la contraseña cumpla todos los requisitos y no necesitamos más que devolver un true para que ésto sea verdad y pase. Más adelante ya se referencia un caso en el que la contraseña es muy corta y ya necesitamos hacer una comprobación para eso, por lo que aplicamos el mínimo código para comprobar si la contraseña pasada cumple la longitud requerida o no.\nTÉCNICAS DE TESTEO Pongamos el caso de que tenemos una función que queremos testear, pero ésta función llama a otra función. Nosotros queremos comprobar que la primera función funciona, por lo que la llamada a la segunda función simplemente está ahí porque forma parte de la implementación. En un caso en el que ambas funciones realizan algo sencillo no debería importar demasiado, pero si la segunda función realice un comportamiento complejo que requiera de un tiempo de ejecucción considerable no nos interesa que nuestro test gaste fuerzas en cosas que ya están testadas por otro lado. Cuando realizamos pruebas, podemos recurrir a simular objetos o funciones (Mocks), es decir, ese objeto lo tenemos disponible para usar en nuestro test pudiendo llamar a sus métodos, pero al ser una simulación no ejecutara los métodos realmente, por lo que si el método realizase llamadas a base de datos o interactuase con el sistema de archivos realmente no lo estaría haciendo. Además nos ofrece otras ventajas como son simular la entrada de parámetros, verificar que se llama una función, etc.\nLos frameworks de mocks son ideales para poder introducir test en un código legado sin caer en el intento. Cuando nos encontramos con ése código de una clase que tiene 20000 líneas y que llama a muchas otras clases o funciones, podemos ir haciendo mock de lo que no necesitamos testear por el momento, por lo que si en un momento determinado solo queremos probar una clase o sus métodos la mejor opción es mockear y ver que simplemente pasa lo correcto a llamadas externas que realiza.\nPara usar éstos Mocks en Java lo podemos realizar de la siguiente forma:\nPara que nuestra clase de los test pueda usar los métodos de Mockito tengo que extender de su clase.Para mockear un objeto basta con usar el método mock.Tenemos para usar método de mockitos como \u0026lsquo;when\u0026rsquo; con el cual podemos usar \u0026lsquo;any()\u0026rsquo; para simular que se le pasó lo que ese método necesita, y si combinamos el \u0026lsquo;when\u0026rsquo; con un \u0026rsquo;thenReturn\u0026rsquo; podemos comprobar que cuando se ejecuta devuelve lo que esperamos, o incluso un \u0026rsquo;thenThrow\u0026rsquo; para cuando lanza una excepción. Otro método interesante es \u0026lsquo;verify\u0026rsquo; que comprobará que se llama a la función de un objeto.\n@ExtendWith(MockitoExtension.class) class RegisterVolunteerActionShould { TemplateService templateService = mock(TemplateService.class); EmailService emailService = mock(EmailService.class); @Test void send_confirmation_email(){ when(templateService.getEmailConfirmationTemplate(any())).thenReturn(new EmailTemplate(template)); verify(emailService).sendEmail(any()); } } También tenemos otro tipo de dobles que son los fakes, que bien puede ser una base de datos en memoria, repositorio en memoria, un servidor de correo que realmente no envía correo. Todo para que tengamos la funcionalidad más parecida o igual al artefacto real pero facilitándonos la vida en los test simplificando la forma en que hace las validaciones.\nERRORES TÍPICOS AL HACER TDD Infravalorar el nombre de los test es una muy mala decisión. Pensar un buen nombre para nuestro test nos da un mayor conocimiento de lo que estamos haciendo y esperamos, además de que un test con un buen nombre que nada más leerlo ya sabes que debería estar haciendo, se convierte en documentación viva del proyecto para que no nos perdamos ni nosotros ni futuros desarrolladores que vean nuestro código.\nComo tanto cuidamos sus nombres, debemos cuidar su presentación. Es fundamental ir aplicando refactor tras ver los verdes para tener test legibles y fáciles de mantener. El código de nuestros test es tan importante como el de producción.\nSi tenemos un test en rojo está en rojo, no intentemos desviarnos ignorando que eso está ahí para no perder la eficacia de nuestra colección de test. Debemos arreglar ésto antes de seguir en el desarrollo del proyecto para garantizar que seguimos cubiertos en lo mayormente posible.\nNuestro test solo busca verificar que se cumple un solo comportamiento del sistema, por lo que intentar meter otro tipo de comportamiento es un error. Solo así entenderemos mejor cuando falle el por qué lo hace, y estaremos teniendo a la vez una documentación precisa de como va avanzando nuestro código.\nNo necesitamos complejidad ciclomática introduciendo bucles o condicionales en nuestros test, no queremos que nuestro test corra el riesgo de fallar por factores ajenos al comportamiento que está testando, así como no queremos un aumento de su complejidad.\nCONCLUSIÓN TDD es una forma de codificar, cada cual es libre de seguir su metodología preferida, pero los beneficios están ahí. Cuando nos acostumbramos a trabajar de ésta forma y empezamos a ver su gran utilidad podemos desarrollar un código honesto, ese código que no pretende ser perfecto (porque eso no existe en la programación), si no que pretende tener un constante feedback con el desarrollador y ser lo más simple posible. Aprender a usarlo en un día no es posible, es la práctica quien hará de nosotros unos buenos analizadores de código que se plantean primero los problemas antes de saltar al estilo kamikaze a la acción. Para empezar, la mejor forma es realizarlo con katas (ejercicios cortos de programación) que nos obliguen a pensar en su solución, pudiendo así practicar desde lo más básico de TDD. El mob programming es perfecto para aprender más rápido, vemos las posibles soluciones de otros, sus puntos de vista y los contrastamos con los nuestros para determinar cual será la mejor solución o incluso darnos cuenta de a veces hay muchos casos que no se nos pasan por la cabeza.\n","permalink":"https://raulpadilladelgado.github.io/blog/p/an%C3%A1lisis-del-libro-dise%C3%B1o-%C3%A1gil-con-tdd/","summary":"\u003cp\u003e\u003cimg alt=\"Portada libro\" loading=\"lazy\" src=\"https://d2sofvawe08yqg.cloudfront.net/tdd-en-castellano/hero?1576861322\"\u003e\u003c/p\u003e\n\u003ch1 id=\"introducción\"\u003e\u003cstrong\u003eINTRODUCCIÓN\u003c/strong\u003e\u003c/h1\u003e\n\u003cp\u003e\u0026ldquo;Diseño Ágil con TDD\u0026rdquo;, por Carlos Ble, es un libro muy interesante que nos enseña como implementar Test-Driven Development en el desarrollo de código. Muestra como basar nuestro código en los Test que escribimos, y no al revés. A continuación comparto mis experiencias leyendo éste libro.\u003c/p\u003e\n\u003ch1 id=\"que-beneficios-nos-aporta\"\u003e\u003cstrong\u003e¿QUE BENEFICIOS NOS APORTA?\u003c/strong\u003e\u003c/h1\u003e\n\u003cp\u003eSe presentan grandes beneficios de codificar de ésta forma. Se habla de conseguir un código simple, que haga lo que necesitamos para cada momento, que cuando falle nos de un correcto y constante feedback de porqué eso está ocurriendo, así como un código legible y fácil de mantener. Obliga a los programadores a pensar primero en cual es la solución que quieren conseguir antes de comenzar a implementarla, y como consecuencia llegar a manejar diversas formas de conseguir lo que se desea teniendo en cuenta más casos de uso\u003c/p\u003e","title":"Análisis del libro \"Diseño ágil con TDD\""},{"content":"En la capa de lógica de nuestra aplicación se encuentra el código más personal del programador, ese código que no necesita basarse en primitivos para cumplir tipos de datos en transferencias porque es otra capa quien lo hará por ésta.\nAnteriormente he definido \u0026ldquo;el código más personal del trabajador\u0026rdquo;, y me refiero a que la estructura y legibilidad del código en ésta capa depende de como se implemente. Si no se nos exigen primitivos, podemos crear nuestros propios tipos para que cuando se lea el código mejoremos la expresividad y legibilidad, en una capa como la que menciono en la que se encuentra el núcleo de la aplicación y a su vez el código que más desarrollo y razonamiento necesita.\nCon tus propios tipos se te abre un mundo de posibilidades para simplificar tu código. En el siguiente ejemplo tenemos una clase que utiliza un String como un texto, y verifica que cuando sea null retorne un String vacío.\npublic String wordWrap(String text, int columnWidth){ if (text==null){ return \u0026#34;\u0026#34;; } } Esta clase no debería estar haciendo éstas comprobaciones, su misión es única, solo debería implementar la función para la que fue pensada, por lo que refactorizando lo anterior podemos crear nuestro propio tipo delegando la construcción a un método de factoría que llamará al constructor y que cuando reciba un null lo transforme a un String vacío. El porqué usar un método de factoría en lugar del constructor como tal se debe a que un constructor solo debería realizar lo que su nombre indica, construir un objeto, por lo que cualquier otro tipo de implementación dentro de éste solo conseguiría empeorar la legibilidad en nuestro código.\n/*Texto.java*/ public class Texto{ private String texto; private Texto(String texto) {this.texto=texto;} public static Texto crearTexto(String texto){ if (texto==null){texto=\u0026#34;\u0026#34;;} return new Texto(texto); } } /*application.java*/ public String wordWrap(Texto texto, int columnWidth){ } Listo, la clase en cuestión ya solo tiene que preocuparse de implementar su funcionalidad, ya que hemos llevado el String inicial a nuestro propio tipo \u0026ldquo;Texto\u0026rdquo;, el cuál tiene un método para realizar la anterior comprobación, y así la podemos extraer de \u0026ldquo;application,java\u0026rdquo;.\nHemos creado un Value Object, lo que quiere decir que su estado no mutará a lo largo del tiempo no como si lo hace una entidad. Como ya hemos visto, este tipo de objetos nos permiten volver nuestro código más legible y bonito, logrando así que no solo nosotros lo entendamos perfectamente, si no que también lo haga una persona que no desarrolló dicho código.\nCuando creamos nuestro propios tipos, debemos tener claro que cada clase debe tener una responsabilidad. Una clase que utiliza métodos de otra, debería estar accediendo a ésta para obtener datos, no para suplantar la identidad y desarrollar un comportamiento que no debería ser suyo. Estamos hablando de un code smell al que se le denomina Feature Envy. Para verlo más claro, en el siguiente ejemplo:\npublic class Phone { private final String unformattedNumber; public Phone(String unformattedNumber) { this.unformattedNumber = unformattedNumber; } public String getAreaCode() { return unformattedNumber.substring(0,3); } public String getPrefix() { return unformattedNumber.substring(3,6); } public String getNumber() { return unformattedNumber.substring(6,10); } } public class Customer... private Phone mobilePhone; public String getMobilePhoneNumber() { return \u0026#34;(\u0026#34; + mobilePhone.getAreaCode() + \u0026#34;) \u0026#34; + mobilePhone.getPrefix() + \u0026#34;-\u0026#34; + mobilePhone.getNumber(); } tenemos una clase \u0026ldquo;Phone\u0026rdquo; que tiene como propiedad un número de teléfono (String), y mediante métodos se puede acceder a dicha propiedad y extraer el \u0026ldquo;areacode\u0026rdquo;, \u0026ldquo;prefix\u0026rdquo; y \u0026ldquo;number\u0026rdquo;. Por otro lado, la clase \u0026ldquo;Customer\u0026rdquo;, que accede a clase anterior para conseguir un número de teléfono (String) accediendo a los métodos de ésta para implementar su propia función de formatear un número de teléfono a un estado más legible.\nEs un código funcional, pero ¿no debería ser la primera clase (Phone) la que desarrolle dicha funcionalidad?, al fin y al cabo es la clase que sabe operar con números de teléfono, por lo que la segunda clase (Customer) no debería saber hacer eso ya que su comportamiento es otro. Si arreglamos un poco lo anterior nos queda algo así:\npublic class Phone { private final String unformattedNumber; public Phone(String unformattedNumber) { this.unformattedNumber = unformattedNumber; } public String getMobilePhoneNumber() { return \u0026#34;(\u0026#34; + getAreaCode() + \u0026#34;) \u0026#34; + getPrefix() + \u0026#34;-\u0026#34; + getNumber(); } public String getAreaCode() { return unformattedNumber.substring(0,3); } public String getPrefix() { return unformattedNumber.substring(3,6); } public String getNumber() { return unformattedNumber.substring(6,10); } } public class Customer... private Phone mobilePhone; private String emailAddress; public String dataOfCustomer(){ return mobilePhone.getMobilePhoneNumber() + emailAddress; } Se implementó lo anterior en la primera clase (Phone), por lo que ahora la segunda (Customer) accede a éste método para obtener lo que desea y hemos conseguido que cada clase cumpla con su misión sin que ninguna se entrometa en el funcionamiento de otro, y no intente implementar cosas que no debe conocer\n","permalink":"https://raulpadilladelgado.github.io/blog/p/evita-primitive-obsession/","summary":"\u003cp\u003eEn la capa de lógica de nuestra aplicación se encuentra el código más personal del programador, ese código que no necesita basarse en primitivos para cumplir tipos de datos en transferencias porque es otra capa quien lo hará por ésta.\u003c/p\u003e\n\u003cp\u003eAnteriormente he definido \u0026ldquo;el código más personal del trabajador\u0026rdquo;, y me refiero a que la estructura y legibilidad del código en ésta capa depende de como se implemente. Si no se nos exigen primitivos, podemos crear nuestros propios tipos para que cuando se lea el código mejoremos la expresividad y legibilidad, en una capa como la que menciono en la que se encuentra el núcleo de la aplicación y a su vez el código que más desarrollo y razonamiento necesita.\u003c/p\u003e","title":"Evita primitive obsession"},{"content":"Lombok es una biblioteca Java que nos permite reemplazar las líneas de código que creamos para los constructores, getter y setter, entre otros, por unas simple anotaciones, por lo que cuando creamos una clase solo definimos las propiedades y ésta librería hace el resto.\nCon una simple anotación(@Data), Lombok inyectará los métodos getter y setter para cada propiedad, además de un equals, hashCode y toString.\n@Getter y Setter Se usan para generar un getter y setter para un atributo específico.\n/*BEFORE*/ public boolean isEmployed() { return employed; } public void setEmployed(final boolean employed) { this.employed = employed; } /*AFTER*/ @Getter @Setter private boolean employed; @NonNull Cuando se coloca en un atributo para el que Lombok está generando un método de establecimiento, se generará una comprabación que dará como resultado un NullPointerException, si se proporciona un valor nulo.\n/*BEFORE*/ public class Family{ private List\u0026lt;Person\u0026gt; members; public Family(List\u0026lt;Person\u0026gt; members) { if (members == null) throw new java.lang.NullPointerException(\u0026#34;members\u0026#34;); this.members = members; } public void setMembers(List\u0026lt;Person\u0026gt; members) { if (members == null) throw new java.lang.NullPointerException(\u0026#34;members\u0026#34;); this.members = members; } } /*AFTER*/ @Setter @NonNull private List\u0026lt;Person\u0026gt; members; @ToString Genera una implementación del método toString. Imprimirá todos los atributos en forma de clave-valor, para todos los campos que no sean estáticos. Podemos personalizar su funcionamiento mediante los siguientes parámetros:\nincludeFieldNames = false → No incluye el nombre de los atributos (clave) exclude=\u0026ldquo;nombreDeAtributo\u0026rdquo; → No incluye el atributo especificado of → Lista de atributos a imprimir callSuper = true → Junta el toString de la clase actual con el de la clase de la que extiende /*BEFORE*/ public class Foo extends Bar { private boolean someBoolean = true; private String someStringField; private float someExcludedField; @java.lang.Override public java.lang.String toString() { return \u0026#34;Foo(super=\u0026#34; + super.toString() + \u0026#34;, someBoolean=\u0026#34; + someBoolean + \u0026#34;, someStringField=\u0026#34; + someStringField + \u0026#34;)\u0026#34;; } } /*AFTER*/ @ToString(callSuper=true,exclude=\u0026#34;someExcludedField\u0026#34;) public class Foo extends Bar { private boolean someBoolean = true; private String someStringField; private float someExcludedField; } @EQUALSANDHASHCODE Generará una implementación de los métodos equals y hashCode, para comparar un objeto de la clase con otro. Le podemos especificar una serie de parámetros:\nexclude=\u0026ldquo;nombreDeAtributo\u0026rdquo; → No compara por el atributo especificado of → Lista de atributos a comparar callSuper = true → Compara también por la clase de la que extiende /*BEFORE*/ public class Person extends SentientBeing { enum Gender { /*public static final*/ Male /* = new Gender() */, /*public static final*/ Female /* = new Gender() */; } @NonNull private String name; @NonNull private Gender gender; private String ssn; private String address; private String city; private String state; private String zip; @java.lang.Override public boolean equals(final java.lang.Object o) { if (o == this) return true; if (o == null) return false; if (o.getClass() != this.getClass()) return false; if (!super.equals(o)) return false; final Person other = (Person)o; if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false; if (this.gender == null ? other.gender != null : !this.gender.equals(other.gender)) return false; if (this.ssn == null ? other.ssn != null : !this.ssn.equals(other.ssn)) return false; return true; } @java.lang.Override public int hashCode() { final int PRIME = 31; int result = 1; result = result * PRIME + super.hashCode(); result = result * PRIME + (this.name == null ? 0 : this.name.hashCode()); result = result * PRIME + (this.gender == null ? 0 : this.gender.hashCode()); result = result * PRIME + (this.ssn == null ? 0 : this.ssn.hashCode()); return result; } } /*AFTER*/ @EqualsAndHashCode(callSuper=true,exclude={\u0026#34;address\u0026#34;,\u0026#34;city\u0026#34;,\u0026#34;state\u0026#34;,\u0026#34;zip\u0026#34;}) public class Person extends SentientBeing { enum Gender { Male, Female } @NonNull private String name; @NonNull private Gender gender; private String ssn; private String address; private String city; private String state; private String zip; } @Data Combina la funcionalidad de @ToString, @EqualsAndHashCode, @Getter, y @Setter, todo lo que un Plain Old Java Object (POJO) necesita. También se le pueden especificar parámetros como:\nstaticConstructor = \u0026ldquo;metodoFactoria\u0026rdquo; → El constructor se vuelve privado y se crea un método factoría con el nombre especificado. /*BEFORE*/ public class Company { private final Person founder; private String name; private List\u0026lt;Person\u0026gt; employees; private Company(final Person founder) { this.founder = founder; } public static Company of(final Person founder) { return new Company(founder); } public Person getFounder() { return founder; } public String getName() { return name; } public void setName(final String name) { this.name = name; } public List\u0026lt;Person\u0026gt; getEmployees() { return employees; } public void setEmployees(final List\u0026lt;Person\u0026gt; employees) { this.employees = employees; } @java.lang.Override public boolean equals(final java.lang.Object o) { if (o == this) return true; if (o == null) return false; if (o.getClass() != this.getClass()) return false; final Company other = (Company)o; if (this.founder == null ? other.founder != null : !this.founder.equals(other.founder)) return false; if (this.name == null ? other.name != null : !this.name.equals(other.name)) return false; if (this.employees == null ? other.employees != null : !this.employees.equals(other.employees)) return false; return true; } @java.lang.Override public int hashCode() { final int PRIME = 31; int result = 1; result = result * PRIME + (this.founder == null ? 0 : this.founder.hashCode()); result = result * PRIME + (this.name == null ? 0 : this.name.hashCode()); result = result * PRIME + (this.employees == null ? 0 : this.employees.hashCode()); return result; } @java.lang.Override public java.lang.String toString() { return \u0026#34;Company(founder=\u0026#34; + founder + \u0026#34;, name=\u0026#34; + name + \u0026#34;, employees=\u0026#34; + employees + \u0026#34;)\u0026#34;; } } /*AFTER*/ @Data(staticConstructor=\u0026#34;of\u0026#34;) public class Company { private final Person founder; private String name; private List\u0026lt;Person\u0026gt; employees; } @Builder Usaremos el patrón Builder sin necesidad de implementar código para ello, bastante útil también en la ejecución de test (unit test por ejemplo) en donde debemos crear el objeto con atributos válidos o por defecto.\n@Getter @Builder public class Widget { private final String name; private final int id; } /************************************/ Widget testWidget = Widget.builder() .name(\u0026#34;foo\u0026#34;) .id(1) .build(); assertThat(testWidget.getName()) .isEqualTo(\u0026#34;foo\u0026#34;); assertThat(testWidget.getId()) .isEqualTo(1); ","permalink":"https://raulpadilladelgado.github.io/blog/p/lombok-annotations/","summary":"\u003cp\u003eLombok es una biblioteca Java que nos permite reemplazar las líneas de código que creamos para los constructores, getter y setter, entre otros, por unas simple anotaciones, por lo que cuando creamos una clase solo definimos las propiedades y ésta librería hace el resto.\u003c/p\u003e\n\u003cp\u003eCon una simple anotación(@Data), Lombok inyectará los métodos getter y setter para cada propiedad, además de un equals, hashCode y toString.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"https://1.bp.blogspot.com/\u0026ndash;CYarvynlTU/Xp24u-wh6zI/AAAAAAAAAKE/-pW3SGJ7qokEtPL93HgN911_hjvzZCnnQCLcBGAsYHQ/s400/28ae0b5bfcf57c9972db8ecc6f9df091_f652.png\" loading=\"lazy\" src=\"/blog/p/lombok-annotations/images/28ae0b5bfcf57c9972db8ecc6f9df091_f652.png\"\u003e\u003c/p\u003e\n\u003ch1 id=\"getter-y-setter\"\u003e\u003cstrong\u003e@Getter y Setter\u003c/strong\u003e\u003c/h1\u003e\n\u003cp\u003eSe usan para generar un getter y setter para un atributo específico.\u003c/p\u003e","title":"Lombok annotations"},{"content":"Inicio Arrancando con un proyecto Para crear un proyecto de una forma rápida y sencilla he encontrado Spring Initializr. Es muy simple, basta con elegir lenguaje, versión y otras configuraciones, como los metadatos del proyecto, y finalmente las dependencias del proyecto Spring que usaremos.\nSi elegimos \u0026ldquo;generar\u0026rdquo; nos descarga un archivo zip que cual contiene el proyecto creado ya preparado para empezar a trabajar con él.\nEstructura de un proyecto En el archivo pom.xml tenemos la configuración que realizamos en Spring Initializr.\nEn src → main → Java → com.example.demo, tenemos una clase DemoApplication que tiene un método main con algo de código, que nos servirá para comprobar que todo está bien y que nuestro proyecto está preparado para ser usado.\nEn src → main → Resources, tenemos dos carpetas para guardar archivos para nuestra aplicación web, como plantillas.\nInyección de dependencias Repositorios Por convención para guardar datos se usan los repositorios. A la clase que definamos como repositorio le añadimos la anotación \u0026ldquo;@Repository\u0026rdquo; para que el framework entienda que se usara dicha clase como la capa de repositorio.\n@Repository public class PersonaRepositoryImpl implements PersonaRepository { private static Logger LOG = LoggerFactory.getLogger(PruebaSpringBootApplication.class); @Override public void registrar(String nombre) { LOG.info(\u0026#34;SE REGISTRO A \u0026#34;+ nombre); } } Servicios Es la capa de lógica de negocio. La clase debe llevar la anotación \u0026ldquo;@Service\u0026rdquo; para indicar que funcionará como la capa de negocio. Si ponemos el ejemplo de que éste servicio actúa sobre un repositorio, podemos crear una instancia del repositorio con la etiqueta \u0026ldquo;@Autowired\u0026rdquo;, para que sea el propio Spring quien se encargue de crear una instancia de la clase.\n@Service public class PersonaServiceImpl implements PersonaService { @Autowired private PersonaRepository repo; @Override public void registrar(String nombre) { repo.registrar(nombre); } } Qualifier Para tener varias implementaciones de una interfaz usaremos la etiqueta \u0026ldquo;@Qualifier\u0026rdquo;. En éste ejemplo tenemos dos tipos de personas (repositorios), y después desde un servicio accederemos a uno de los repos con la misma anotación.\n@Repository @Qualifier(\u0026#34;persona1\u0026#34;) public class PersonaRepositoryImpl implements PersonaRepository { private static Logger LOG = LoggerFactory.getLogger(PruebaSpringBootApplication.class); @Override public void registrar(String nombre) { LOG.info(\u0026#34;SE REGISTRO A \u0026#34;+ nombre); } } /**************************************************************************************/ @Repository @Qualifier(\u0026#34;persona2\u0026#34;) public class PersonaRepositoryImpl2 implements PersonaRepository { private static Logger LOG = LoggerFactory.getLogger(PruebaSpringBootApplication.class); @Override public void registrar(String nombre) { LOG.info(\u0026#34;SE REGISTRO A \u0026#34;+ nombre + \u0026#34;(persona2)\u0026#34;); } } /**************************************************************************************/ @Service public class PersonaServiceImpl implements PersonaService { @Autowired @Qualifier(\u0026#34;persona2\u0026#34;) private PersonaRepository repo; @Override public void registrar(String nombre) { repo.registrar(nombre); } } Spring Data (JPA) Vamos a crear una aplicación que se conecte a una base de datos usando JPA, un módulo que facilita la implementación de repositorios basados en JPA. Creamos una carpeta \u0026ldquo;modelos\u0026rdquo; y dentro una clase que definirá el modelo de nuestra tabla en la BD.\nPersona.java\n@Entity public class Persona { @Id private int idPersona; @Column(name = \u0026#34;nombre\u0026#34;,length = 50) private String nombre; public int getIdPersona() { return idPersona; } public void setIdPersona(int idPersona) { this.idPersona = idPersona; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } } Después, creamos dentro de \u0026ldquo;repositorios\u0026rdquo; una interfaz que extienda de \u0026ldquo;JpaRepository\u0026rdquo;.\nIPersonaRepositorio.java\npublic interface IPersonaRepositorio extends JpaRepository\u0026lt;Persona,Integer\u0026gt; { } Es necesario que el archivo application.properties tenga configurado lo siguiente:\nspring.jpa.database=POSTGRESQL spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://localhost/Welcome spring.datasource.username=postgresql spring.datasource.password=passwword spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true El siguiente paso es crear la base de datos desde pgAdmin 4 (Postgresql), y ejecutar la aplicación para que se nos cree la estructura de tablas.\nPara que se nos cree una tabla en la BD y meter datos dentro de ésta, definiremos nuestro método para insertar datos a la tabla.\nWelcomeController.java\n@Autowired private IPersonaRepositorio repositorio; @GetMapping(\u0026#34;/welcome\u0026#34;) public String welcome(@RequestParam(name=\u0026#34;name\u0026#34;,required = false,defaultValue = \u0026#34;suso\u0026#34;) String name, Model model) { Persona p = new Persona(); p.setIdPersona((int) repositorio.count()); p.setNombre(name); repositorio.save(p); model.addAttribute(\u0026#34;name\u0026#34;, name); return \u0026#34;welcome\u0026#34;; } Tendremos otro método que listara todo el contenido de la tabla. Le crearemos una plantilla para mostrar los datos guardados en el modelo.\nWelcomeController.java\n@GetMapping(\u0026#34;/listar\u0026#34;) public String todasLasPersonas(Model model) { model.addAttribute(\u0026#34;personas\u0026#34;,repositorio.findAll()); return \u0026#34;personas\u0026#34;; } Ahora nos queda recorrer en la vista la lista de personas guardadas en el Model.\nPersonas.html\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34; xmlns:th=\u0026#34;\u0026lt;http://www.thymeleaf.org\u0026gt;\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;table\u0026gt; \u0026lt;th:block th:each=\u0026#34;persona : ${personas}\u0026#34;\u0026gt; \u0026lt;tr\u0026gt; \u0026lt;td th:text=\u0026#34;${persona.idPersona}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;td th:text=\u0026#34;${persona.nombre}\u0026#34;\u0026gt;\u0026lt;/td\u0026gt; \u0026lt;/tr\u0026gt; \u0026lt;/th:block\u0026gt; \u0026lt;/table\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Servicios Rest (CRUD) Se usa para describir cualquier interfaz entre sistemas que utilice directamente HTTP para obtener datos o indicar la ejecución de operaciones sobre los datos en cualquier formato (XML, JSON, etc).\nPara crear un controlador que utilice los servicios Rest utilizamos la anotación \u0026ldquo;@RestController\u0026rdquo;.\nMétodo Get Si queremos definir la operación get, usaremos \u0026ldquo;@GetMapping\u0026rdquo;.\nMétodo Post Si queremos definir la operación Post, usaremos \u0026ldquo;@PostMapping\u0026rdquo;. Es necesario pasar como parámetro \u0026ldquo;@RequestBody Objeto o\u0026rdquo;.\nMétodo Put Si queremos definir la operación Put, usaremos \u0026ldquo;@PutMapping\u0026rdquo;. Funciona igual que el Post, pero con la diferencia de que si encuentra un id ya existente la operación será una actualización.\nMétodo Delete Si queremos definir la operación Delete, usaremos \u0026ldquo;@DeleteMapping\u0026rdquo;, que llevar como parámetro el valor (id) que se recoge en la ruta. En el argumento del método, recogemos el id mediante \u0026ldquo;@PathVariable(id)\u0026rdquo;\nRestDemoController.java\n@RestController public class RestDemoController { @Autowired private IPersonaRepositorio repositorio; @GetMapping public List\u0026lt;Persona\u0026gt; listar(){ return repositorio.findAll(); } @PostMapping public void insertar(@RequestBody Persona persona){ repositorio.save(persona); } @PutMapping public void modificar(@RequestBody Persona persona){ repositorio.save(persona); } @DeleteMapping(value = \u0026#34;/{id}\u0026#34;) public void eliminar(@PathVariable(\u0026#34;id\u0026#34;) Integer id){ repositorio.deleteById(id); } } MVC Thymeleaf Controladores Por un lado tenemos los controladores, anotamos la clase mediante \u0026ldquo;@Controller\u0026rdquo;. Los métodos que se desea que funcionen como ruta de nuestra aplicación web, debe tener la anotación \u0026ldquo;@GetMapping(\u0026lsquo;ruta_a_elegir\u0026rsquo;)\u0026rdquo;.\n@Controller public class MainController { @GetMapping(\u0026#34;/welcome\u0026#34;) public String welcome(@RequestParam(name=\u0026#34;name\u0026#34;,required = false,defaultValue = \u0026#34;suso\u0026#34;) String name, Model model){ model.addAttribute(\u0026#34;name\u0026#34;,name); return \u0026#34;Welcome\u0026#34;; } } Si se pasa parámetro en la ruta, se recoge mediante \u0026ldquo;@RequestParam\u0026rdquo; en éste caso en la variable name(String) y se guarda en la variable model(Model), la cual lo usamos para pasar datos a las vistas.\nVistas Para crear las vistas añadimos nuestras páginas html en la carpeta \u0026ldquo;Templates\u0026rdquo;.\nEl controlador debe indicar en el return el nombre de la vista a la que se dirige.\nNecesitamos referenciar en la etiqueta html el motor de plantillas de thymeleaf (xmlns:th=\u0026quot;http://www.thymeleaf.org\u0026quot;). Para incrustar texto en una etiqueta usando dicho motor, usamos \u0026ldquo;th:text\u0026rdquo;, al cual le podemos indicar una variable guardada en el objeto model con \u0026ldquo;${variable}\u0026rdquo;.\n\u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34; xmlns:th=\u0026#34;\u0026lt;http://www.thymeleaf.org\u0026gt;\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Title\u0026lt;/title\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;p th:text=\u0026#34;\u0026#39;Bienvenido, \u0026#39;+${name}\u0026#34;/\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Spring security Éstas dependencias nos crearán automáticamente un login para nuestra aplicación web, en el que podemos editar el usuario y contraseña de la siguiente manera en el archivo application.properties spring.security.user.name = pady spring.security.user.password= 1234 Es un ejemplo poco práctico, ya que lo óptimo sería comprobar los usuarios mediante una base de datos. Creamos la clase usuario y su repositorio.\nUsuario.java\n@Entity public class Usuario { @Id private int id; private String nombre; private String clave; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public String getClave() { return clave; } public void setClave(String clave) { this.clave = clave; } } IUsuarioRepositorio.java\npublic interface IUsuarioRepositorio extends JpaRepository\u0026lt;Usuario,Integer\u0026gt; { } Agregamos nuestro usuario a la tabla\nSpringDataApplicationTests.java\n@Autowired private IUsuarioRepositorio repositorio_usuarios; @Test void crearUsuario() { Usuario usuario = new Usuario(); usuario.setId(0); usuario.setClave(\u0026#34;1234\u0026#34;); usuario.setNombre(\u0026#34;pady\u0026#34;); Usuario actual = repositorio_usuarios.save(usuario); assertEquals(\u0026#34;1234\u0026#34;,actual.getClave()); } Vamos a crear una clase de configuración en nuestro proyecto, en la que usaremos un @Bean de Spring para cifrar las contraseñas que se guardan en la base datos.\nSecurityConfig.java\n@Configuration public class SecurityConfig { @Bean public BCryptPasswordEncoder passwordEncoder(){ BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder; } } Finalmente lo instanciamos en la clase donde interese, y usamos su método encode.\nSpringDataApplicationTests.java\n@Autowired private BCryptPasswordEncoder encoder; @Test void crearUsuario() { Usuario usuario = new Usuario(); usuario.setId(1); usuario.setClave(encoder.encode(\u0026#34;1234\u0026#34;)); String expected = usuario.getClave(); usuario.setNombre(\u0026#34;pady\u0026#34;); Usuario actual = repositorio_usuarios.save(usuario); assertEquals(expected,actual.getClave()); } Para habilitar @WebSecurity necesitamos que nuestra clase de configuración extienda de WebSecurityConfigureAdapter . Sobreescibrimos los métodos configure para indicar de donde sacaremos los usuarios a comparar. Pero primero debemos crear una clase UserService que extienda de UserDetailsService , en la cual instanciamos el repositorio de usuarios, y sobrescribimos el método de la clase extendida. Para configurar el método de la clase extendida, tenemos que tener un método que busque por un nombre de usuario, y no por un id, así que vamos a la interfaz repositorio de usuarios y creamos dicho método.\nIUsuarioRepositorio.java\npublic interface IUsuarioRepositorio extends JpaRepository\u0026lt;Usuario,Integer\u0026gt; { Usuario findByNombre(String nombre); } UserService.java\n@Service public class UserService implements UserDetailsService { @Autowired private IUsuarioRepositorio repositorio; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Usuario encontrado = repositorio.findByNombre(username); List\u0026lt;GrantedAuthority\u0026gt; roles = new ArrayList\u0026lt;\u0026gt;(); roles.add(new SimpleGrantedAuthority(\u0026#34;ADMIN\u0026#34;)); UserDetails userDet = new User(encontrado.getNombre(),encontrado.getClave(),roles); return userDet; } } SecurityConfig.java\n@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserService userServiceDetails; @Autowired private BCryptPasswordEncoder bcrypt; @Bean public BCryptPasswordEncoder passwordEncoder(){ BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userServiceDetails).passwordEncoder(bcrypt); } @Override protected void configure(HttpSecurity http) throws Exception { super.configure(http); } } PROYECTO DE EJEMPLO\n⬇️ ⬇️ ⬇️ ⬇️ ⬇️ ⬇️\nPruebaSpringBoot\nRESUMEN\nTodo lo necesario para implementar un login mediante JPA, utilizando Postgresql como motor de base de datos.\nDefinir un modelo @Entity public class Usuario { @Id private int id; private String nombre; private String clave; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public String getClave() { return clave; } public void setClave(String clave) { this.clave = clave; } } Crear un repositorio public interface UsuarioRepository extends JpaRepository\u0026lt;Usuario,Integer\u0026gt; { Usuario findByNombre(String nombre); } Servicio de usuarios @Service public class UsuarioService implements UserDetailsService { @Autowired private UsuarioRepository repositorio; @Autowired private BCryptPasswordEncoder encoder; public void crearUsuario() { Usuario usuario = new Usuario(); usuario.setId((int)repositorio.count()+1); usuario.setClave(encoder.encode(\u0026#34;1234\u0026#34;)); usuario.setNombre(\u0026#34;pady\u0026#34;); repositorio.save(usuario); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Usuario encontrado = repositorio.findByNombre(username); List\u0026lt;GrantedAuthority\u0026gt; roles = new ArrayList\u0026lt;\u0026gt;(); roles.add(new SimpleGrantedAuthority(\u0026#34;ADMIN\u0026#34;)); UserDetails userDet = new User(encontrado.getNombre(),encontrado.getClave(),roles); return userDet; } } Security Config @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UsuarioService userServiceDetails; @Autowired private BCryptPasswordEncoder bcrypt; @Bean public BCryptPasswordEncoder passwordEncoder(){ BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); return bCryptPasswordEncoder; } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userServiceDetails).passwordEncoder(bcrypt); } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(\u0026#34;/\u0026#34;).permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage(\u0026#34;/login\u0026#34;) .permitAll() .and() .logout() .permitAll(); } } Creación de usuarios @SpringBootTest class PracticaSpringBootApplicationTests { @Autowired private UsuarioService servicio; @Test void contextLoads() { servicio.crearUsuario(); } } ","permalink":"https://raulpadilladelgado.github.io/blog/p/spring-boot-primer-contacto/","summary":"\u003ch1 id=\"inicio\"\u003eInicio\u003c/h1\u003e\n\u003ch2 id=\"arrancando-con-un-proyecto\"\u003eArrancando con un proyecto\u003c/h2\u003e\n\u003cp\u003ePara crear un proyecto de una forma rápida y sencilla he encontrado \u003ca href=\"https://start.spring.io/\"\u003eSpring Initializr\u003c/a\u003e. Es muy simple, basta con elegir lenguaje, versión y otras configuraciones, como los metadatos del proyecto, y finalmente las dependencias del proyecto Spring que usaremos.\u003c/p\u003e\n\u003cp\u003eSi elegimos \u0026ldquo;generar\u0026rdquo; nos descarga un archivo zip que cual contiene el proyecto creado ya preparado para empezar a trabajar con él.\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/blog/p/spring-boot-primer-contacto/images/Untitled.png\"\u003e\u003c/p\u003e\n\u003ch2 id=\"estructura-de-un-proyecto\"\u003eEstructura de un proyecto\u003c/h2\u003e\n\u003cp\u003eEn el archivo pom.xml tenemos la configuración que realizamos en Spring Initializr.\u003c/p\u003e","title":"Spring boot, primer contacto"},{"content":"Value object En el modelo Value Object, un objeto se diferencia de otro por su contenido, no por su identidad propia.\nPodemos entender el concepto de Value Object con el ejemplo de las monedas. Aunque cada moneda de 1 euro tiene su propia identidad (un número de serie), la economía funciona porque entiende que una moneda de 1 euro es igual que otra moneda de un euro, ambas valen lo mismo y a efectos prácticos son iguales.\nfunction createCoin() { return { value: 1 } } Los estados en un modelo Value Object son inmutables. Si queremos cambiar una característica, crearemos un nuevo objeto con esa característica, en lugar de ir modificando los estados de un único objeto. Un objeto que tiene variables de estado y que nunca cambia se llama objeto inmutable. Por lo tanto, utilizaré el modelo de Value Object cuando tenga sentido crear nuevos objetos en lugar de confiar en un único objeto e ir cambiando su estado.\nString en JS es un objeto inmutable y lo tratamos como Value Object. Por ello, todos los métodos que se aplican sobre string generan un nuevo string y no modifican el original.\nvar string = \u0026#39;Hello”; string.replace(“H”, “x”); /*xello*/ string /*Hello*/ Entity object Este tipo de objetos se comparan en función de su identidad. Son iguales si tienen la misma identidad, sin importar el contenido o valor. También es usual encontrarlos con atributos de identificación.\nEn este modelo, el objeto tiene una identidad única e irrepetible. Además, el modelo permite que el estado del objeto vaya cambiando a lo largo del tiempo. Por lo tanto, aquello que tiene sentido que mute a lo largo del tiempo, suele implementarse como Entity Object.\nfunction user() { let id = Math.random(); let age = 23; return { getAge: function() { return age; }, birthday: function() { ++age; }, getId: function() { return id; } } } ","permalink":"https://raulpadilladelgado.github.io/blog/p/value-object-vs-entity-object/","summary":"\u003ch1 id=\"value-object\"\u003eValue object\u003c/h1\u003e\n\u003cp\u003eEn el modelo Value Object, un objeto se diferencia de otro por su contenido, no por su identidad propia.\u003c/p\u003e\n\u003cp\u003ePodemos entender el concepto de Value Object con el ejemplo de las monedas. Aunque cada moneda de 1 euro tiene su propia identidad (un número de serie), la economía funciona porque entiende que una moneda de 1 euro es igual que otra moneda de un euro, ambas valen lo mismo y a efectos prácticos son iguales.\u003c/p\u003e","title":"Value object VS Entity object"},{"content":"DAO DAO encapsula el acceso a la base de datos. Por lo que cuando la capa de lógica de negocio necesite interactuar con la base de datos, va a hacerlo a través de la API que le ofrece DAO. Generalmente esta API consiste en métodos CRUD (Create, Read, Update y Delete). Entonces por ejemplo cuando la capa de lógica de negocio necesite guardar un dato en la base de datos, va a llamar a un método create(). Lo que haga este método, es problema de DAO y depende de como DAO implemente el método create(), puede que lo implemente de manera que los datos se almacenen en una base de datos relacional como puede que lo implemente de manera que los datos se almacenen en ficheros de texto. Lo importante es que la capa de lógica de negocio no tiene porque saberlo, lo único que sabe es que el método create() va a guardar los datos, así como el método delete() va a eliminarlos, el método update() actualizarlos, etc. Pero no tiene idea de como interactúa DAO con la base de datos.\nEn una aplicación, hay tantos DAOs como modelos. Es decir, en una base de datos relacional, por cada tabla, habría un DAO.\nDAO consiste básicamente en una clase que es la que interactúa con la base de datos. Los métodos de esta clase dependen de la aplicación y de lo que queramos hacer. Pero generalmente se implementan los métodos CRUD para realizar las “4 operaciones básicas” de una base de datos.\nDTO Los DTO (Data Transfer Object) son utilizados por DAO para transportar los datos desde la base de datos hacia la capa de lógica de negocio y viceversa. Por ejemplo, cuando la capa de lógica de negocio llama al método create(), ¿qué es lo que hace DAO? inserta un nuevo dato… ¿pero qué dato? el que la capa de lógica de negocio le pase como parámetro… ¿y cómo se lo pasa este dato? bueno, a través de un DTO.\nTiene como finalidad crear un objeto plano (POJO) con una serie de atributos que puedan ser enviados o recuperados del servidor en una sola invocación, de tal forma que un DTO puede contener información de múltiples fuentes o tablas y concentrarlas en una única clase simple.\nDado que el objetivo de un DTO es utilizarlo como un objeto de transferencia entre el cliente y el servidor, es importante evitar tener operaciones de negocio o métodos que realicen cálculos sobre los datos, es por ello que solo deberemos de tener los métodos GET y SET de los respectivos atributos del DTO.\nPor ejemplo, si tuviéramos una base de datos relacional con una tabla empleados, con los campos id, nombre y salario. Entonces tendríamos que crear una clase EmpleadoDTO, con los atributos id, nombre y salario, que van a utilizar la capa de negocio y de persistencia para transportar los datos entre las dos capas.\nEntonces cuando la capa de lógica de negocio quiera guardar un dato en la base de datos, va a crear un objeto EmpleadoDTO, a través de los accessors va a modificar los atributos, y después se lo va a pasar al método create() de DAO. Entonces DAO va a leer los datos del DTO, y los va a guardar en la base de datos.\nEntidad vs DTO Las entidades son clases que fueron diseñadas para mapear contra la base de datos, no para ser una vista para una pantalla o servicio determinado. Lo óptimo sería tener un DTO con todos los datos recogidos de la base de datos, y no modificar la entidad ya que nos llevará a una restructuración de la base de datos que busca cubrir los requerimientos de transferencia de datos, dejando de lado el verdadero propósito de la entidad, que es únicamente mapear contra la base de datos\n","permalink":"https://raulpadilladelgado.github.io/blog/p/patrones-de-dise%C3%B1o-dao-y-dto/","summary":"\u003ch1 id=\"dao\"\u003eDAO\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003eDAO encapsula el acceso a la base de datos.\u003c/strong\u003e Por lo que cuando la capa de lógica de negocio necesite interactuar con la base de datos, va a hacerlo a través de la API que le ofrece DAO. Generalmente esta API consiste en métodos CRUD (Create, Read, Update y Delete). Entonces por ejemplo \u003cstrong\u003ecuando la capa de lógica de negocio necesite guardar un dato en la base de datos\u003c/strong\u003e, va a llamar a un método create(). \u003cstrong\u003eLo que haga este método, es problema de DAO\u003c/strong\u003e y depende de como DAO implemente el método create(), puede que lo implemente de manera que los datos se almacenen en una base de datos relacional como puede que lo implemente de manera que los datos se almacenen en ficheros de texto. Lo importante es que la capa de lógica de negocio no tiene porque saberlo, lo único que sabe es que el método create() va a guardar los datos, así como el método delete() va a eliminarlos, el método update() actualizarlos, etc. Pero no tiene idea de como interactúa DAO con la base de datos.\u003c/p\u003e","title":"Patrones de diseño (DAO y DTO)"}]