tag:blogger.com,1999:blog-47561109564364814262024-03-06T01:41:50.177+01:00Hijos del SpectrumDesarrollo de videojuegos/aplicaciones, Juegos retro y otros gustos personales.Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.comBlogger110125tag:blogger.com,1999:blog-4756110956436481426.post-69009911518392309472022-02-14T11:01:00.000+01:002022-02-14T11:01:01.276+01:00Github por consola usando personal token<p>Desde agosto de 2021 ya no se puede usar el usuario y contraseña para hacer operaciones desde la consola y es necesario usar un token personal.</p>
<p>Evidentemente, memorizar un token (o varios) es una locura, con lo que cachearlo de una forma segura es la más adecuado.</p>
<p>Existen <a href="https://docs.github.com/en/get-started/getting-started-with-git/caching-your-github-credentials-in-git">varias formas de lograrlo</a>. Se puede usar Github CLI, pero personalmente prefiero usar git que es más genérico.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://miro.medium.com/max/700/1*BCZkmZR1_YzDZy22Vn4uUw.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="292" data-original-width="700" height="167" src="https://miro.medium.com/max/700/1*BCZkmZR1_YzDZy22Vn4uUw.png" width="400" /></a></div><p></p>
<h3 style="text-align: left;">Creando el token</h3><p>Lo primero que hace falta es <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token">crear un token</a>. Sobre nuestra foto de perfil pinchamos y seguimos esta ruta:</p>
<p>Settings > Developer settings > Personal access tokens</p><p>Le pongo un nombre al nuevo token, la caducidad y marco sólo el scope "repo"</p>
<div class="nota">NOTA: Dependiendo de lo que se use pueden hacer falta marcar otras opciones como workflow, si las necesitamos se rechazará por ejemplo nuestro push y nos indicará si tenemos que dar más permisos que implicaría crear de nuevo otro token.
</div>
<p>En mi caso me creo uno por dispositivo, servicio u ocasión (como en un ordenador compartido para una demo) y así puedo tener más controlado los que están activos y en caso de creer que está comprometido puedo revocarlo. Así que le pongo el nombre del dispositivo y en mi ordenador trabajo habitual lo pongo sin caducidad.</p>
<p>Al crear el token lo copiamos, porque luego desaparece, y lo usamos para comprobar que funciona en el mismo lugar que antes introducíamos la contraseña.</p>
<p>Hacemos la prueba <a href="https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls">clonando un repositorio</a> usando HTTP.</p>
<div class="nota">NOTA: En esta guía usamos la forma más simple para un usuario particular. <a href="https://stackoverflow.com/questions/18935539/authenticate-with-github-using-a-token">Hay muchas formas distintas</a> que cada uno tendrá evaluar si son más adecuadas (SSH, doble factor, etc...).
</div>
<br />
<h3 style="text-align: left;">Cacheando las credenciales</h3>
<p>Hay varios métodos para <a href="https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage">guardar credenciales con la ayuda de git</a>.<br /></p>
<p>Mi preferido es configurar el archivo para almacenarlas para mi usuario en mi home con el comando:</p>
<div class="codigo">
git config --global credential.helper 'store --file ~/.mis-credenciales-git'
</div>
<p>Una vez configurado, se utiliza la primera vez el nombre de usuario y el token como contraseña y ya estará almacenado.</p>
<p>Si lo hacemos global hay que tener en cuenta que almacenará, en claro, todas las credenciales que usemos en todos los repositorios (github, gitlab, etc...). Se puede ver lo que ha guardado simplemente abriendo el fichero.</p>
<p>Si por cualquier razón pensáramos que el archivo está comprometido simplemente se anula el token en github (ojo si contiene más credenciales). Si ya no quisiéramos usar un fichero para el cacheo es buena idea borrarlo.<br /></p>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-28799856928098583872021-06-08T10:10:00.001+02:002021-06-13T10:49:46.549+02:00Ejecutar API para entornos distintosAhora ya tenemos dos entornos y no parece cómodo estar cambiando los archivos de propiedades por ejemplo para ejecutar la API usando una BD u otra. <b>Spring tiene la capacidad de activar perfiles</b> que pueden modificar entre otras cosas los beans o propiedades que use la aplicación.<br />
<br />
Esta entrada no tratará sobre usar perfiles, pero valiéndonos de ellos, <b>vamos a ejecutar nuestra API apuntando a una BD o a otra en función del perfil/entorno donde queramos ejecutar</b>.<br />
<br />
Vamos a mantener el fichero de propiedades de desarrollo (el que usa H2) para el perfil por defecto y nos vamos a crear otro fichero llamado application-prod.properties para las propiedades que quiero sobrescribir para este perfil (y que tendrá las credenciales de Postgre).<br />
<br />
El fichero <code>application-prod.properties</code> quedaría así:<br />
<pre><code>spring.datasource.url=jdbc:postgresql://kandula.db.elephantsql.com:5432/usuario
spring.datasource.username=usuario
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
logging.level.es.lanyu=INFO</code></pre>
Y ahora vamos a duplicar nuestra configuración de ejecución y le vamos a poner un nombre que quede claro que es para producción. En esta configuración vamos a añadir una propiedad a nuestra JVM que es la de <code>spring.profiles.active=prod</code>. En la casilla correspondiente añadimos esto precedido de <code>-D</code>:<br />
<pre><code>-Dspring.profiles.active=prod</code></pre>
<img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFLmOErcoxnvsxr6uhEBDraL0hQ0GVtE-XXgRZXhIkmPHlQXqF6pYUF8vLFawvsipBsqu4bSMe-lJJuRoFzPD96D-8SDXplotOejWSRw3cOJ9BTGZh6-b-IdXkaVglMRCkiNeNsDvQ-i-_/s667/Run+configuration.png" style="margin: auto; width: 95%;" />
<p>Si ejecutamos con la nueva configuración de ejecución esta propiedad va a estar establecida, pero nuestra ejecución de bootRun no la cogerá. Vamos a hacernos una tarea que coja ese valor y lo establezca en la tarea bootRun. Añadimos este código a nuestro <code>build.gradle</code>:<br />
</p><pre><code>task cloneJvmProfile {
ext.profile = System.getProperty("spring.profiles.active")
if (profile != null) {
println "Setting $profile profile in bootRun"
bootRun.systemProperties.put("spring.profiles.active", profile)
}
}</code></pre>
<p>Ahora ya podemos ejecutar nuestra anterior configuración de ejecución en Eclipse para levantar la API apuntando a H2 o usar la nueva para apuntar a Postgre.</p>
<p>Del mismo modo <b>ahora se puede establecer el perfil a usar en Heroku para que use el de producción y ya no tenemos que cambiar nuestros ficheros <code>.properties</code></b>. Nos vamos a nuestra app de Heroku > Settings > Config Vars y añadimos la clave valor siguiente:<br />
</p>
<pre><code>SPRING_PROFILES_ACTIVE = prod</code></pre>
<img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjB0ZCmZGoiOLta2YocLLhsiTnmoKKQqE9JoeugmoadgxpcSoKrA-qrJIzDxqAAq9VhMseRrmjrdge5OogmLK6BjHCpWUnhA2v5dByDwBvoSjmpN7IgfWaHAVK3m0bM_Pl6bIYtrRm6HXFb/s678/vars.png" style="margin: auto; width: 95%;" />
<p>Puedes conseguir el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/0dd312f35c4ecbf683d229a6de24d19821fef686">código hasta aquí</a> en su repositorio. </p>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-86452695804810090932021-02-16T17:22:00.001+01:002021-02-16T17:22:48.451+01:00Streaming con OBS en LAN<p>El objetivo de esta guía es hacer streaming de un equipo en una red LAN. Se va a usar un equipo con tarjeta gráfica NVIDIA (usando una GTX 750Ti, comprobar que tiene NVENC la tarjeta gráfica) y Ubuntu (probado en Ubuntu 18.04).<br /></p><p>Se instala OBS: <a href="https://obsproject.com">https://obsproject.com</a><br /></p><p>Se ejecuta el asistente de OBS para ver configuración recomendada. Si detecta NVENC es que se puede codificar con la tarjeta NVIDIA, si no hay que instalar los drivers apropiados:</p>
<pre><code>sudo ubuntu-drivers autoinstall</code></pre>
<p>Con esto ya debería ejecutarse el asistente y salir NVENC.</p><p>Después hay que montarse un servidor web para servir el video. Uso nginx como en este post: <a href="https://obsproject.com/forum/threads/broadcasting-in-a-lan.20637">https://obsproject.com/forum/threads/broadcasting-in-a-lan.20637</a></p>
<div class="nota">OBS uses the rtmp protocol to stream. This depends on servers to
distribute the stream to the viewers, thats where nginx fits in. You can
run it on any of the two machines and under windows just fine. <br />
<br />
Lets say PC1 has the lan ip 192.168.0.5 and PC2 has .0.6, we run nginx
on PC1 (with a simple configuration file that has one application called
"live1" which awaits your stream). In OBS on PC1 we setup a livestream
to a custom service and can use rtmp://127.0.0.1/live1 or
rtmp://192.168.0.5/live1 for the server address. <br />
As playpath use "camera" without the quotes. You probably already
configured your scenes in OBS (added the Camera) so you could now start
streaming.<br />
<br />
On the second PC, load up VLC and use open network stream. As the
address this time we have to use rtmp://192.168.0.5/live1/camera so we
combine the network ip with the application and the playpath so VLC can
request the stream from nginx. So after clicking Play, you should see
your camera feed.
<br />
For nginx, I host a compiled package for Windows at <a class="link link--external" href="http://rtmp.jack0r.com/" rel="noopener" target="_blank">http://rtmp.jack0r.com/</a>
it comes with an example config file so is pretty much ready to go.
(Has the live1 app already) Of course if you have a question, just post
it below and if you want some more general guides about nginx check out:
<a class="link link--external" href="http://www.helping-squad.com/server/" rel="noopener" target="_blank">http://www.helping-squad.com/server/</a></div>
<p>En mi caso lo hago con docker:</p>
<ol style="text-align: left;">
<li>Instalar docker: <a href="https://docs.docker.com/engine/install/ubuntu">https://docs.docker.com/engine/install/ubuntu</a></li>
<li>Y uso esta imagen: <a href="https://hub.docker.com/r/tiangolo/nginx-rtmp">https://hub.docker.com/r/tiangolo/nginx-rtmp</a></li>
<li>Una vez arrancado el nginx voy a OBS a la <b>Configuración</b> de <b>Emisión</b> y elijo la opción Personalizado/Custom. En servidor pongo <code>rtmp://127.0.0.1/live</code> y en clave pongo el nombre de la escena para emitir (en el ejemplo pongo <code>prueba</code>)</li>
<li>Luego en otro ordenador de la LAN abro VLC y uso la ubicación de red <code>rtmp://<ip como 192.168.?.?>/live/<clave - así que ponemos prueba></code></li>
<li>El video me llega con retraso de algunos segundos pero se ve muy bien</li><li>Habría que probar con las gráficas integradas a ver si es suficiente. Por ejemplo para un aula de informática con ordenadores donde es suficiente ver el código a pocos FPS y sin audio no debería haber problemas. Si se dispone de dos monitores en cada puesto es mejor que usar un proyector.<br /></li>
</ol>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com3tag:blogger.com,1999:blog-4756110956436481426.post-41170640252703609512020-12-19T13:20:00.002+01:002020-12-19T13:20:59.409+01:00Un return vs varios returns<p>La pregunta sobre si es mejor un sólo <code>return</code> o de dónde viene esta idea es muy habitual en los foros de programación. Hay varios puntos de vista a este respecto como es de suponer y se pueden ver en <a href="https://softwareengineering.stackexchange.com/questions/118703/where-did-the-notion-of-one-return-only-come-from">una de las preguntas con más actividad</a> al respecto.</p>
<p>En la <a href="https://softwareengineering.stackexchange.com/a/118793">respuesta aceptada</a> habla que en realidad viene de la programación estructurada y cuenta el ejemplo con FORTRAN.</p>
<p>Una razón que considero importante, y es la que más me convence, es uno de los comentarios a esta respuesta:</p>
<div class="nota">"<span class="comment-copy">This doesn't just apply to assembly. There
was also some research done at Microsoft (on C codebases) that found
multiple returns contributed to higher bug frequency. See: <a href="https://rads.stackoverflow.com/amzn/click/com/1570740550" rel="nofollow noreferrer">amazon.com/Writing-Solid-Code-20th-Anniversary/dp/1570740550/…</a></span><span class="comment-copy">"</span></div>
<p>Lo cuál ya es un <b>fundamento basado en la experiencia y no sólo en opiniones</b> subjetivas de qué lee mejor cada uno.</p>
<p>Él resultado de ese estudio podría explicarse con que si hace falta varios <code>return</code>s es posible que estés en un <a href="https://en.wikipedia.org/wiki/God_object">God Object</a> y tengas que dividir funcionalidad para seguir <a href="https://es.wikipedia.org/wiki/SOLID">SOLID</a> (principio de responsabilidad única). Por eso <a href="https://blog.mjouan.fr/if-else-trees-vs-solid-principles/">usando buenas prácticas es raro que encuentres un caso para varios <code>return</code>s</a> o no se solucione simplemente con el operador ternario <code>?:</code></p>
<p>Pero hay una excepción que me parece razonable: la cláusula de guarda (<a href="http://wiki.c2.com/?GuardClause">Guard Clause</a>), la cual <a href="https://stackoverflow.com/a/8493256">tiene sentido</a>.</p>
<p>Éste sería un <a href="https://stackoverflow.com/a/268187">ejemplo con tres cláusulas</a>.</p>
<pre><code>double getPayAmount() {
if (isDead) return deadAmount();
if (isSeparated) return separatedAmount();
if (isRetired) return retiredAmount();
return normalPayAmount();
};</code></pre>
<p>Y seguiría la verdadera recomendación que es <a href="https://stackoverflow.com/a/733858">minimizar los returns</a>.</p>
<p>Por último está la idea extremista de que en efecto sólo haya un <code>return</code>, pero porque en POO sólo deberían utilizarse las palabras reservadas <code>new</code> y <code>return</code> y el resto <a href="https://www.yegor256.com/2015/08/18/multiple-return-statements-in-oop.html">debe ser todo manejado por objetos</a>:</p>
<pre><code>public int max(int a, int b) {
return new If(
new GreaterThan(a, b),
a, b
);
}</code></pre>
<h3 style="text-align: left;">Conclusión<br /></h3>
<p>En mi opinión, parece que <b>una Guard Clause podría tener sentido ya que evita validaciones externas al método y es lo primero que se vería en una revisión</b>, manteniendo un único return más al final. <b>En el resto de casos sería mejor evitar varios returns por los datos del estudio citado</b> justo antes y porque parece que en general es más fácil de mantener (depurar y hacer cambios en el código) a costa de ser menos verbose que parece el principal argumento en contra de un único <code>return</code>.</p>
<p>Como es un tema más bien de debate, te invito a que pongas tu punto de vista en los comentarios.</p>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-49791167901538128442020-12-11T09:06:00.001+01:002021-04-03T11:57:43.271+02:00Wildcards <?><p>Para terminar esta introducción a genéricos nos faltaría ver las <a href="https://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html">wildcards</a> que nos permiten poder definir un tipo variable usando como identificador del parámetro el carácter "<code>?</code>".</p><p>Es muy habitual verlo en la firma de los métodos para admitir cualquier <code>Collection</code> que admita un tipo de elemento o cualquiera de los subtipos de ese elemento, ya que <code>Collection<Vehiculo></code> no es asignable a <code>Collection<Coche></code>. En este ejemplo vamos a definir un método que admita esos dos tipos de colecciones:</p>
<pre><code>private static void imprimirFlota(Collection<? extends Vehiculo> vehiculos) {
vehiculos.forEach(System.out::println);
}</code></pre>
<p>Su utilidad la vemos a continuación:</p>
<pre><code>Collection coleccionRaw = Arrays.asList(new Coche(), new CocheProducto("", "", 10000.0f));
Collection<Vehiculo> vehiculos = coleccionRaw;
// Collection<Coche> coches = vehiculos; // Error
imprimirFlota(vehiculos);
Collection<Coche> coches = coleccionRaw;
imprimirFlota(coches);</code></pre>
<div class="nota">NOTA: Puede parecer que lo mejor es no definir el tipo ya que "no tiene el problema" que tenemos si lo definimos. Eclipse nos dará un warning tanto al declarar la variable (dirá que debemos declararlo) como al asignarla (nos advierte que no se puede checkear la seguridad de tipo). En resumen, hay que definirlo por seguridad de nuestro código en tiempo de compilación y porque de lo contrario no tendremos acceso a los miembros más allá de los pertenecientes a <code>Object</code>.
</div>
<p>Sin tener más conocimiento sobre genéricos no recomiendo usar las wildcards en otro contexto ya que lo más normal es que tengamos muchos problemas. No obstante se puede<a href="https://www.tutorialspoint.com/java_generics/java_generics_wildcards_guidelines.htm"> usar como guía estas guidelines</a>.<br /></p>
<p>Hay más conceptos sobre genéricos, de hecho hay <a href="https://www.oreilly.com/library/view/java-generics-and/0596527756/">libros enteros</a> sólo sobre este tema con términos como <a href="https://docs.oracle.com/javase/tutorial/java/generics/erasure.html">type erasure</a>, pero que no se van a ver en esta introducción de fundamentos, aunque hay que tenerlos presentes pues no conocerlos puede darnos quebraderos de cabeza (lo típico que te dice que no se puede implementar dos veces la misma interface; prueba a implementar <code>Comparable<Coche></code> en la clase <code>Coche</code> y te encontrarás con el mensaje: <i>"The interface Comparable cannot be implemented more than once with different arguments: <code>Comparable<Vehiculo></code> and <code>Comparable<Coche></code>"</i> que lo causa el type erasure).</p><p>Genéricos (desde Java 5) al igual que <a href="https://www.oracle.com/technical-resources/articles/java/ma14-java-se-8-streams.html">streams</a> o <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html">lambdas</a> (desde Java 8) son grandes avances pero quedan fuera del alcance que se pretende en esta parte de fundamentos. Puedes encontrar <a href="https://docs.oracle.com/javase/tutorial/">más tutoriales</a> en la página oficial de Oracle.<br /></p>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-66866110141615322112020-12-11T09:05:00.000+01:002020-12-11T09:05:14.333+01:00Limites en genéricos (boundary)<p>Cuando necesitamos limitar las opciones de un tipo variable podemos establecer los límites adecuados en la declaración del parámetro tipo. En nuestro ejemplo de Identificable vamos a obligar que el tipo <code>T</code> implemente <code>Comparable<T></code>. El código quedaría así:</p>
<pre><code>public interface Identificable<T extends Comparable<T>> {
T getId();
}</code></pre>
<p>De esta forma sólo va a poder admitirse tipos que se encuentren dentro de los límites. Podemos probarlo intentando asignar a <code>T</code> un tipo que no sea comparable (aunque sea absurdo prueba con <code>ProductExterno</code> por ejemplo).</p>
<p>Ésto nos permite contar con otros miembros garantizados, en este caso podemos usar el método <code>compareTo(Identificable<T> identificable)</code> y podríamos definir un orden natural para todos los identificables usando el método <code>getId()</code>. El código definitivo sería:</p>
<pre><code>public interface Identificable<T extends Comparable<T>> extends Comparable<Identificable<T>> {
T getId();
@Override
default int compareTo(Identificable<T> identificable) {
return getId().compareTo(identificable.getId());
}
}</code></pre>
<div class="nota">NOTA: Para que fuera correcto tendríamos que quitar la implementación de <code>Comparable<Vehiculo></code> en la clase <code>Vehiculo</code> por el concepto type erasure que se menciona en la siguiente entrada.</div>
<p>Los límites no se ciñen a un único tipo, puedes hacer una composición de ellos (pero que sea posible, no puedes decir que sea de más de una clase siendo que no existe la herencia múltiple). En este caso voy a definir un tipo <code>T</code> en un método para que admita <code>Identificable<Long></code> y <code>Arrancable</code>:</p>
<pre><code>public static void main(String[] args) {
admiteCoche(new Coche());
}
private static <T extends Identificable<Long> & Arrancable> void admiteCoche(T identificableArrancable) {
System.out.println(identificableArrancable.getId());
identificableArrancable.arrancar();
}</code></pre>
<p>En este caso admitirá <code>Coche</code>, pero no <code>Moto</code> (ya que no es <code>Identificable</code>). La definición del tipo parámetro debe hacerse antes del tipo retorno ya que podría usarse T para el retorno y debería estar definido ya en ese caso.<br /></p>
<p>Cuando en el límite quiero añadir una clase debo poner la clase la primera y luego irían todas las interfaces que hagan falta (intenta hacerlo al revés). Éste es un ejemplo pidiendo un <code>Coche</code> y <code>Comerciable</code>, con lo que sólo admitirá un <code>CocheProducto</code>:</p>
<pre><code>private static <T extends Coche & Comerciable> void admiteCocheProducto(T cocheComerciable) {
System.out.println(cocheComerciable.getPrecio() + "|" + cocheComerciable.getClass());
}</code></pre>
<p>Como vemos siempre se define un tipo parámetro que termina definiéndose y, dentro de su scope se mantiene fijo (puede ser definido en una clase que implemente un tipo genérico o tomado de un parámetro en un método/constructor). Sin embargo existe la opción de no definir un tipo específico y dejar que se admita sin hacer la declaración de tipo parámetro. Ésto se conoce como wildcard y lo vemos en la siguiente entrada.</p>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-26338684292664234442020-12-10T18:26:00.000+01:002020-12-10T18:26:02.289+01:00Tipo variable<p>Para rematar esta parte de fundamentos vamos a ver el tipo por referencia que nos falta que es el <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.4">Tipo Variable</a>. Lo hemos usado al utilizar interfaces como <code>Collection</code> o <code>Comparable</code>. Ahora vamos a aprender a definirlo. Como dice la documentación, el tipo variable es introducido por la declaración de un parámetro tipo en una clase, interface, método o constructor genérico.</p>
<p>Esto quiere decir que en nuestro código vamos a referirnos a un tipo por su identificador dentro de su ámbito (scope) pero sin definir qué tipo concreto es. Podemos aplicarle unos límites (por ejemplo decir que debe cumplir con una interface, heredar de una clase o una mezcla de ello) como veremos en la siguiente entrada.</p>
<p>En este caso voy a tomar como ejemplo un identificador para una clase. Puede que no tenga claro el tipo que quiero usar para identificarlo, podría ser un número, un <code>String</code> o cualquier otro. Al no tener claro el tipo podría verme bloqueado a no poder seguir programando, sin embargo desde que existen los tipos variables puedo hacer código genérico reutilizable para distintos tipos.</p>
<p>Así pues me voy a crear una interface genérica que refleje esto:</p>
<pre><code>public interface Identificable<T> {
T getId();
}</code></pre>
<p>Como vemos se marca un tipo <code>T</code> que por defecto puede ser de cualquier tipo. La sintaxis es rodeándolo por los caracteres <code><</code> y <code>></code> siendo habitual usar la letra <code>T</code>. Si se necesitan varios parámetros de tipo habría que usar más letras (también podías usar un identificador distinto a una única mayúscula). Otros ejemplos típicos si hay que usar más de un parámetro tipo son usar <code>R</code> para tipos retorno, <code>K</code> para claves o <code>V</code> para valores (ver ejemplos en las <a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">interfaces funcionales de Java 8</a>). Ahora podemos usarlo añadiendo un <code>id</code> a <code>Coche</code>. En mi caso voy a identificar los coches por la matrícula así que <code>T</code> sería un <code>String</code>. El código quedaría así:</p>
<pre><code>public class Coche extends VehiculoConRuedas implements Identificable<String> {
...
@Override
public String getId() {
return getMatricula();
}
}</code></pre>
<p>No obstante, podía plantearse escenarios muy habituales donde una matrícula no sería el identificador que se necesitaría. Podría cambiarlo rápidamente a un <code>Long</code>:</p>
<pre><code>public class Coche extends VehiculoConRuedas implements Identificable<Long> {
...
private Long id;
@Override
public Long getId() {
return id;
}
}</code></pre>
<p>Como podemos observar, al cambiar la definición de <code>T</code> el método anteriormente implementado ya no sirve y debe devolver el nuevo tipo <code>Long</code>. Al ser tipos por referencia se pide que <code>T</code> sea un tipo por referencia, es decir, no puedo usar como tipo variable un tipo primitivo, si lo intentara me saldría un error y no compilaría.</p>
<p>No obstante muchas veces no sirve con dejar completamente abierto <code>T</code> si quiero implementar código ya que sólo podríamos contar con los miembros de <code>Object</code>. Por ejemplo con <code>Collection</code> o <code>Comparable</code> no pasa nada porque debe admitir todo, pero en nuestro ejemplo si quisiéramos implementar por defecto un orden natural usando <code>T</code> deberíamos limitarlo a un <code>Comparable<T></code>. Cómo se hace lo vemos en la siguiente entrada.<br /></p>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-4932434544484935502020-12-10T09:18:00.002+01:002020-12-10T09:18:34.447+01:00Integrando con código externo<p>En las entradas anteriores hemos visto las diferencias entre clases e interfaces. Por ahora hemos utilizado tipos nativos de Java pero hay otras muchas librerías que nos hacen la vida más fácil sin tener que reinventar la rueda.</p><p>Sin entrar en frameworks concretos y con la idea puesta que nuestro código tendrá que ampliar sistemas que ya estén en producción, añadir librerías de terceros o incluso integrar distintos componentes<b> ¿Cómo podría utilizar tipos de otra librería sobre los que no quiero/puedo modificar el código fuente para integrarlos con mi negocio? ¿Cómo podría usar funcionalidades de terceros que me son útiles para utilizarlos con los objetos de mi negocio?</b></p>
<p>Sin entrar en cómo añadir librerías que lo veremos más adelante, en esta entrada vamos a simular que tuviera una clase y una interface de un tercero y cómo las integro en mi código.</p>
<h3 style="text-align: left;">Supuesto <br /></h3><p>Imaginemos que ahora tengo que integrar mi código sobre vehículos con una librería que me llevase el tema de comercializarlos. <b>La idea es buscar una librería abierta que tenga mucho apoyo de la comunidad</b>, que se vea que se está manteniendo (ver fecha último commit) y que los "issues" que se van generando son atendidos.</p>
<p>En nuestro caso no vamos a buscar una para desviarnos del tema, pero vamos a simular una que estuviera diseñada para el uso de interfaces. Ésto sabemos que nos permitiría implementar esas interfaces en nuestro código para utilizar sus métodos. La desventaja de esto es que nuestro código estaría acoplado a esa librería así que hay que decidir si de verdad la queremos como dependencia o si queremos poder cambiarla fácilmente en un futuro.</p>
<h3 style="text-align: left;">Caso 1.- Usar una clase externa dentro de mi negocio</h3>
<p>Vamos a partir de una clase <code>Product</code> externa definida así:</p>
<pre><code>package com.github.commerce;
public class Product implements Merchantable {
private String description;
private float price;
@Override
public String getDescription() {
return description;
}
@Override
public float getPrice() {
return price;
}
public Product(String description, float price) {
this.description = description;
this.price = price;
}
}</code></pre>
<p>Y una interface <code>Merchantable</code> definida así:</p>
<pre><code>package com.github.commerce;
public interface Merchantable {
String DEFAULT_CURRENCY = "€";
static Double priceToDouble(Merchantable merchantable) {
return new Double(merchantable.getPrice());
}
String getDescription();
float getPrice();
default String getString() {
return getDescription() + " (" + getPrice()
+ DEFAULT_CURRENCY + ")";
}
}</code></pre>
<div class="nota">NOTA: Tanto <code>Product</code> y <code>Merchantable</code>, así como el resto de código externo, vamos a suponer que no sería modificable por nosotros. De hecho vendría compilada como una dependencia aunque aquí la añadamos a nuestro código porque no hemos visto cómo añadir dependencias todavía.<br /></div>
<p>Por nuestra parte tenemos una nueva interface <code>Comerciable</code>:</p>
<pre><code>package vehiculos;
public interface Comerciable {
String getDescripcion();
float getPrecio();
}</code></pre>
<p>Y un par de clases para implementar <code>Comerciable</code>. Una para utilizar nuestro tipo Coche:</p>
<pre><code>public class CocheProducto extends Coche implements Comerciable {
private float precio;
@Override
public float getPrecio() {
return precio;
}
public CocheProducto(String modelo, String color, float precio) {
super(modelo, color);
this.precio = precio;
}
@Override
public String getDescripcion() {
return getModelo();
}
}
</code></pre>
<p>Y otra para aprovechar el código nuestro con <code>Product</code>:</p>
<pre><code>public class ProductExterno extends Product implements Comerciable {
@Override
public String getDescripcion() {
return getDescription();
}
@Override
public float getPrecio() {
return getPrice();
}
public ProductExterno(String description, float price) {
super(description, price);
}
@Override
public String toString() {
return getString();
}
}</code></pre>
<p>Ahora juntamos el código en este ejemplo y todo funciona correctamente:</p>
<pre><code>public class UsandoLibreria {
public static void main(String[] args) {
ProductExterno productoExterno = new ProductExterno("CocheRaro", 25000f);
CocheProducto cocheProducto = new CocheProducto("Seat", "Blanco", 16000);
System.out.println("Usando Comerciable");
Collection<Comerciable> productos = Arrays.asList(
productoExterno,
cocheProducto
);
productos.forEach(System.out::println);
System.out.println(getValorTotal(productos));
}
private static float getValorTotal(Collection<Comerciable> comerciables) {
return (float)comerciables.stream().mapToDouble(UsandoLibreria::toDouble).sum();
}
private static Double toDouble(Comerciable comerciable) {
return new Double(comerciable.getPrecio());
}
}</code></pre>
<p>Ésta es la salida:</p>
<div class="codigo">Usando Comerciable<br/>
CocheRaro (25000.0€)<br/>
Placa null - Seat (Blanco), 4 ruedas<br/>
41000.0</div>
<p>Como vemos en el ejemplo, al estar nuestro diseño orientado a interfaces, podemos usar tanto nuestras propias clases como otras implementando los métodos de nuestras interfaces. En general se trata de usar siempre la mínima interface en nuestras firmas de métodos.</p>
<h3 style="text-align: left;">Caso 2.- Usar nuestros tipos en código de terceros</h3>
<p>Ahora vamos a reutilizar código de otros con nuestros tipos. Para ello vamos a añadir a <code>CocheProducto</code> la interface <code>Merchantable</code>:</p>
<pre><code>public class CocheProducto extends Coche implements Comerciable, Merchantable {
...
@Override
public String getDescription() {
return getDescripcion();
}
@Override
public float getPrice() {
return getPrecio();
}
@Override
public String toString() {
return getString();
}
}
</code></pre>
<p>Y lo utilizamos en nuestro <code>main</code>:</p>
<pre><code>System.out.println("\nUsando Merchantable");
Collection<Merchantable> merchantables = Arrays.asList(
productoExterno,
cocheProducto
);
merchantables.forEach(System.out::println);
System.out.println(merchantables.stream().mapToDouble(Merchantable::priceToDouble).sum());</code></pre>
<p>La salida es:</p>
<div class="codigo">Usando Merchantable<br/>
CocheRaro (25000.0€)<br/>
Seat (Blanco) (16000.0€)<br/>
41000.0</div>
<p>Podemos observar un mismo "<code>toString()</code>" tanto en la clase que hereda de <code>Product</code> como la que hereda de <code>Coche</code>.</p><p>El ejemplo es muy sencillo, pero demuestra las posibilidades de orientar nuestras implementaciones siguiendo estas buenas prácticas. Por supuesto, al ser <code>ProductExterno</code> un <code>Product</code> puede usar todas las funcionalidades de la librería origen. Al autolimitarnos con que el código externo no es modificable (incluso teniendo acceso al código fuente) vamos a estar preparados para que nuestro código pueda ir admitiendo las modificaciones que se hagan desde proyectos externos que arreglen vulnerabilidades o proporcionen mejoras. Es bueno por tanto buscar repositorios que tengan en cuenta asegurar la retrocompatibilidad como lo hace Java.</p><p>Sé que esta entrada puede resultar un poco más complicada de entender que las anteriores, pero ya es el último contenido que se ve de fundamentos. Evidentemente se pueden hacer muchas cosas sin seguir estas prácticas, pero si se siguen el día de mañana se agradecerá el esfuerzo. Tómatelo como una inversión necesaria.</p>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-38700358553158228092020-12-04T08:57:00.001+01:002023-10-17T09:27:28.771+02:00Comparación de objetos (equals vs Comparable y Comparator)<a href="/2018/10/objetos-metodo-equals-vs-operador.html">Vimos que <code>equals</code> es un miembro de <code>Object</code></a>. Sirve para comparar dos objetos y obtener si son iguales/equivalentes.<br />
<br />
Por otra parte existen las interfaces <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html"><code>Comparable</code></a> y <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html"><code>Comparator</code></a> que sirven también para comparar dos objetos, pero las usaremos para ordenar pues nos pueden decir cuan iguales/distintos son dos objetos midiéndolo con un entero (<code>int</code>). Las <b>diferencias</b> entre estas dos formas de comparar las podemos resumir en:<br />
<ol>
<li><b><code>Comparable</code></b>/<b><code>Comparator</code> es una interfaz mientras que <code>equals</code> es un miembro</b> de todos los objetos (todos tienen una implementación).</li>
<li>Todos los objetos pueden comparar su igualdad (usar <code>equals</code>), pero <b>sólo los objetos con <code>Comparable</code> implementada tienen lo que se llama un orden natural</b> con el que ordenarse por defecto.</li>
<li><b><code>Comparable/Comparator</code> admiten un tipo variable <code>T</code></b> (el tipo que pueden comparar). La definición de este tipo variable afectara al parámetro de su <b>único método a implementar: <code>compareTo(T o)</code></b>/<b><code>compare(T o1, T o2)</code></b>.</li>
</ol>
La diferencia fundamental entre <code>Comparable</code> y <code>Comparator</code> es el dónde y el para qué de su funcionalidad:<br />
<ul>
<li><b>Dónde</b>:
<ul>
<li><code>Comparable</code> necesita implementar el método <code>compareTo(T o)</code> como miembro del objeto que se va a comparar con el parámetro <code>o</code>, y son del mismo tipo <code>T</code> (teniendo en cuenta que los subtipos son del tipo de su supertipo).</li>
<li><code>Comparator</code> necesita implementar el método <code>compare(T o1, T o2)</code> como miembro del objeto comparador que se encargará de comparar <code>o1</code> con <code>o2</code>: el comparador es un objeto independiente que no tiene relación con <code>o1</code> ni <code>o2</code>.</li>
</ul>
</li>
<li><b>Para qué</b>: <code>Comparable</code> va a establecer un orden natural para el tipo mientras que <code>Comparator</code> se puede usar para hacer una ordenación puntual, pudiendo tener varios comparadores para ordenar por distintos criterios.</li>
</ul>
<br />
<div class="cita cita-logro">
<div class="cita-texto">
<code>equals</code> nos dice si dos valores son iguales, <code>Comparable</code>/<code>Comparator</code> nos dice cuan iguales son y nos permite ordenar
</div>
<br class="clearBoth" /></div>
<br />
<h3>
¿Cómo funcionan?</h3>
<br />
El único método que debe implementar cada una debe devolvernos:<br />
<ol>
<li>un número negativo si <code>o1</code> es menor que <code>o2</code>,</li>
<li>cero si son iguales (no implica que <code>o1.equals(o2)</code> sea <code>true</code>) o</li>
<li>positivo si es mayor.</li>
</ol>
De esta forma podemos medir cuan distintos son dos objetos y por tanto ordenarlos, lo cual es imposible con <code>equals</code> que devuelve un <code>boolean</code>.<br />
<br />
<div class="nota">Se recomienda encarecidamente que ambos métodos sean coherentes entendido como: <b><code>o1.compareTo(o2)</code>/<code>compare(o1, o2) == 0</code> debería dar el mismo resultado que <code>o1.equals(o2)</code></b>.</div>
<br />
<h3>
¿Cuándo usamos cada una?</h3>
Desde mi experiencia yo tiendo a generar comparadores antes que a implementar <code>Comparable</code>. Mis motivos son:<br />
<ol>
<li>Puedo ordenar un tipo incluso si no tengo acceso al código para implementar <code>Comparable</code></li>
<li>Puedo disponer de varios comparadores como constantes (<code>static final</code>) en un tipo para poder ordenarlos fácilmente, mientras que sólo puedo tener una implementación para <code>Comparable</code></li>
<li><code>Comparator</code> es una interfaz funcional y puedes instanciarlos con lambdas desde Java 8. El código queda muy legible y compacto con lo que no es un problema crearlos como antiguamente que había que hacer un tipo sólo para eso.</li>
</ol>
<div class="nota">Sólo se puede implementar <code>Comparable</code> una vez, si un supertipo ya lo tiene implementado habrá que sobrescribir el método <code>compareTo</code> si debe ser distinta implementación y las comparaciones dos-a-dos podrían salir inconsistentes (ver type erasure cuando se hable de genéricos).<br /></div>
<br />
El consejo definitivo es: <b>haz un comparador a menos que el diseño de un código en concreto te obligue a implementar <code>Comparable</code></b> (por ejemplo que tenga que usarse el tipo como clave de un mapa ordenado o que un tipo genérico imponga la interface <code>Comparable</code>).<br />
<br />
<div class="cita cita-logro">
<div class="cita-texto">
Si no estás obligado a usar <code>Comparable</code> mejor hazte un <code>Comparator</code> para tener el código de ordenamiento separado</div>
<br class="clearBoth" /></div>
<p>Para ver el uso de <code>Comparator</code> termino la entrada añadiendo éste código al de la entrada anterior que ordena los vehículos por su color: </p>
<pre><code class="language-java">System.out.println("\nLista ordenada (por color):");
vehiculos.sort((v1, v2) -> v1.getColor().compareTo(v2.getColor()));
vehiculos.forEach(System.out::println);</code></pre>
Así queda más limpio y separado el código de vehículos y el de ordenación para un caso concreto. Si te fijas en los <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#t0">métodos de <code>Comparator</code></a> verás cómo han potenciado la interfaz desde Java 8 y lo fácil que es tener un comparador que ordene por orden inverso, usando una clave específica (<code>Comparable</code>) o que trate los valores <code>null</code> de forma distinta. Todo son facilidades.Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-22731811208578796842020-11-26T10:02:00.001+01:002021-11-14T19:30:44.235+01:00Interface Comparable<p>En la entrada anterior hemos ordenado una lista con elementos que tenían un orden natural, pero si lo intentamos hacer con una lista de vehículos no vamos a poder ordenarlos. Para poder ordenar una lista sus elementos deben implementar la interface Comparable.</p>
<div class="nota">NOTA: Si lo intentamos sin implementar la interface nos saldrá la excepción <code>java.lang.ClassCastException: class Vehiculo cannot be cast to class java.lang.Comparable</code> que deja bien claro cuál es el problema.</div>
<p>Se declara como cualquier otra interface, pero en este caso necesitamos poner un tipo genérico para indicar qué tipo puede ordenar la implementación. El código queda así:</p>
<pre><code>public class Vehiculo implements Comparable<Vehiculo> {
...
// Nos hacemos un getter porque lo usamos dos veces
private String getModelo() {
return modelo;
}
@Override
public String toString() {
return getModelo() + " (" + getColor() + ")";
}
@Override
public int compareTo(Vehiculo vehiculo) {
return getModelo().compareTo(vehiculo.getModelo());
}
}</code></pre>
<p>Con este código ya se van a poder comparar todos los vehículos por orden alfabético de su modelo. Lo probamos con el siguiente código:</p>
<pre><code>List<Vehiculo> vehiculos = Arrays.asList(
new Coche("Volvo", "Gris"),
new Vehiculo("Triciclo", "Rosa"),
new Moto("Aprilla", "Azul")
);
vehiculos.forEach(System.out::println);
vehiculos.sort(null);
System.out.println("\nLista ordenada (por modelo):");
vehiculos.forEach(System.out::println);</code></pre>
<h3 style="text-align: left;">¿Qué significa el null en el parámetro del tipo <code>Comparator</code>?</h3><p>Como estamos ordenando por el orden natural no le estamos pasando ningún parámetro al método <a href="https://docs.oracle.com/javase/8/docs/api/java/util/List.html#sort-java.util.Comparator-"><code>sort</code></a>. En realidad es como pasarle el comparador de orden natural: <code>Comparator.naturalOrder()</code></p><p>En la siguiente entrada vamos a utilizar este parámetro para ordenar por color (incluso aunque ya tenga implementada la interface <code>Comparable</code>) y a ver las diferencias entre las interfaces <code>Comparable</code> y <code>Comparator</code>.<br /></p>
Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-2911635865683102172020-11-23T15:12:00.001+01:002020-11-26T13:05:16.828+01:00Ordenar colecciones (List)<p>En esta entrada vamos a utilizar una colección de Strings para ver cómo ordenar una colección:</p>
<pre><code>List<String> listaStrings = Arrays.asList("a", "c", "b", "ab");</code></pre>
<p>Ordenar una colección como ésta es muy fácil debido a que hay una forma natural de ordenar cadenas de texto alfabéticamente. Para ello vamos a usar el método <code>sort</code>. Si nuestra variable es del tipo <code>Collection</code> no contaremos con ese método. Una <code>Collection</code> no contempla el orden, para eso debemos usar una <a href="https://docs.oracle.com/javase/8/docs/api/java/util/List.html"><code>List</code></a> como en el ejemplo.<br /></p><p>Una variable nos asegura un tipo y nos permite usar la API de ese tipo, pero el objeto puede tener implementaciones "más completas".</p>
<p>Para ello voy a crearme otra variable de tipo <code>Collection</code> y le asignaré el mismo objeto creado del tipo <code>List</code>:</p>
<pre><code>Collection<String> strings = listaStrings;</code></pre>
<p>Vamos a imprimirnos nuestra colección para ver el estado inicial desordenado:</p>
<pre><code>System.out.println(strings);</code></pre>
<p>Nos da como salida:</p>
<div class="codigo">[a, c, b, ab]</div>
<p>Si intentamos usar el método <code>sort</code> queda claro que un objeto no es lo mismo que una variable.</p>
<pre><code>listaStrings.sort(null);
// strings.sort(); // No aparece siendo el mismo objeto</code></pre>
<p>Después de ordenar volvemos a imprimir y ya nos sale ordenada:</p>
<pre><code>System.out.println(strings);</code></pre>
<div class="codigo">[a, ab, b, c]</div>
<p>También podemos usar <code>Collections</code>, que es una clase de utilidad para las colecciones, pero igualmente nos pide un tipo <code>List</code>:</p>
<pre><code>Collections.sort(listaStrings);</code></pre>
<p>Sin embargo, no siempre vamos a querer ordenar con éste criterio o puede que necesitemos ordenar un tipo que no tiene un orden natural. En la siguiente entrada vemos cómo trabajar con comparadores y la interface <code>Comparable</code>.</p>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-62464357207017613072020-11-19T17:55:00.004+01:002023-10-17T09:26:30.898+02:00Colecciones (Collection)<p>Ahora que ya somos capaces de hacer nuestras clases, interfaces y objetos, con una jerarquía que nos permite reutilizar el código eficientemente y que podemos representarlas en consola y hacer comprobaciones básicas como ver si dos objetos son equivalentes, vamos a empezar a manejar colecciones de objetos usando la interface <a href="https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html"><code>Collection</code></a> (y no un array que es la única forma que hemos visto de usar un grupo de valores). Mirando el enlace anterior podemos ver la jerarquía que tiene ésta interface.<br /></p><p>Vamos a crearnos una colección de <code>Vehiculo</code>s.</p><p>Primero me creo dos vehículos (código ya conocido):</p>
<pre><code>String matricula = "1234ABC";
Coche coche = new Coche("Ford Fiesta", "Rojo"); // ¿por que no usar variable Vehiculo?
coche.setMatricula(matricula);
Vehiculo moto = new Moto("Suzuki", "Verde");</code></pre>
<p>Después me creo una colección y uso estos vehículos para añadirlos a la colección:</p>
<pre><code>Collection<Vehiculo> vehiculos = new ArrayList<>();</code></pre>
<p>En este ejemplo creamos una colección vacía usando la implementación <code>ArrayList<>()</code>. Ahora mismo no entenderéis ésta línea, pero tendrá sentido unas entradas más adelante, por ahora simplemente aceptar que se hace así.</p><p>En las siguientes líneas vemos que se pueden añadir más objetos a la colección, lo cual es una gran ventaja en comparación con los arrays.</p>
<pre><code>vehiculos.add(coche);
vehiculos.add(moto);</code></pre>
<div class="nota">Las tres líneas de código anteriores son "parecidas" (sería una lista fija) a: <code>vehiculos = Arrays.asList(coche, moto);</code></div>
<p>Vamos a imprimir todos nuestros vehículos:</p>
<pre><code class="language-java">System.out.println(vehiculos);</code></pre>
<p>Vemos que nos imprime algo con más significado que cuando lo hacíamos con los arrays y debíamos usar <code>Arrays.toString()</code>.</p><p>También podemos usar esta forma bastante compacta para ver los objetos en líneas distintas:</p>
<pre><code class="language-java">vehiculos.forEach(System.out::println);</code></pre>
<p>Esto es lo que se llama <a href="https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html">método por referencia</a>, pero tampoco lo hemos visto aún, simplemente que quede como ejemplo para ir usándolo y que quede el código más compacto.</p><p>Del mismo modo que podemos añadir elementos se pueden quitar. Aquí es fundamental el método <code>equals</code>, ya que la forma de encontrar el objeto a eliminar es usando ese método:</p>
<pre><code class="language-java">System.out.println("\nQuito la moto");
vehiculos.remove(moto);
vehiculos.forEach(System.out::println);</code></pre>
<h3 style="text-align: left;">¿Qué pasa si usáramos un objeto igual pero no el mismo?</h3><p>Me creo un objeto que sea igual al coche que tengo en la lista: <br /></p>
<pre><code>coche = new Coche("Ford Fiesta", "Blanco");
coche.setMatricula(matricula);</code></pre>
<p>Ahora voy a añadirlo:</p>
<pre><code class="language-java">System.out.println("\nAñado nuevo coche");
vehiculos.add(coche);
vehiculos.forEach(System.out::println);</code></pre>
<p>Y por último lo quito:</p>
<pre><code class="language-java">System.out.println("\nQuito coche " + coche);
vehiculos.remove(coche);
vehiculos.forEach(System.out::println);</code></pre>
<p>Mirando el resultado parece que hubiera algún problema, pero es todo correcto si examinamos la documentación (<a href="https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html#remove-java.lang.Object-"><code>remove</code> elimina una instancia</a>, la primera). Por eso es importante éste método, y no es el único caso donde se va a usar <code>equals</code> ni <code>hashCode</code> como <a href="/2018/10/objetos-metodo-equals-vs-operador.html">ya se dijo en su entrada</a>.</p>
<div class="nota">NOTA: Para eliminar todos deberíamos usar algo como: <code>vehiculos.removeIf(coche::equals);</code>
</div>
<p>Desde ahora ya podemos manejar un conjunto de objetos como una colección usando lo que hemos visto, pero hay muchas cosas que se pueden hacer con una colección como ordenarla. Vamos a verlo en la siguiente entrada de ordenación.</p>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-24828544438082042742020-11-16T13:49:00.001+01:002023-10-17T09:25:51.839+02:00Interfaces - Lo básico<b>Las <a href="https://es.wikipedia.org/wiki/Interfaz_(Java)">Interfaces</a> </b>(<a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html">el tipo por referencia</a>, no una interfaz de usuario) <b>son posiblemente la herramientas más poderosa que tenemos para reutilizar código</b> con la mayor flexibilidad.<br />
<br />
<b>Cuando declaremos una <a href="https://docs.oracle.com/javase/tutorial/java/concepts/interface.html">interfaz lo que vamos a definir es un contrato</a>, un comportamiento externo con el resto de código</b>, que deben asegurar todos los objetos que implementen esa interfaz (recordad que poníamos el ejemplo de una sierra, cualquier sierra debe tener el método <code>cortar</code>, sin embargo también podemos decir que una tijera puede cortar, e incluso el propio papel puede cortarte el dedo XD).<br />
<br />
Estos objetos que implementan (cumplen el contrato) pueden ser <b>de tipos con ninguna relación</b> como hemos visto con el ejemplo de cortar (sierras, tijeras o incluso papel).<br />
<br />
<div class="cita cita-logro">
<div class="cita-texto">
Con las <b>interfaces</b> podemos declarar el <b>comportamiento de tipos completamente distintos</b>
</div>
<br class="clearBoth" /></div>
<br />
Como deben asegurar un comportamiento, <b>todos los miembros declarados en una interfaz son públicos</b>, con lo que no hace falta añadir modificaciones de acceso pues ya se toma por defecto. Si quisiéramos declarar uno con un acceso más restrictivo obtendremos un error. <br />
<br />
Dos de las Interfaces más famosas, en mi opinión, son <span style="font-family: "courier new" , "courier" , monospace;">Collection</span> y <span style="font-family: "courier new" , "courier" , monospace;">Comparable</span>. Éstas interfaces utilizan genéricos y para sacarles todo el partido hay que conocer los Tipos Variables, así que iremos profundizando en ellas poco a poco.<br />
<br />
Para entender bien qué es y cómo usar una interfaz <b>voy a crear una interfaz llamada <span style="font-family: "courier new" , "courier" , monospace;">Arrancable</span></b>, que sólo tenga un método: <span style="font-family: "courier new" , "courier" , monospace;">arrancar()</span>. El código necesario para esto es:<br />
<pre><code>public interface Arrancable {
void arrancar();
}</code></pre>
Vemos que <b>el método <span style="font-family: "courier new" , "courier" , monospace;">arrancar()</span> no tiene cuerpo</b>. Es normal y se escribe de esa forma para declarar que es un método abstracto, es decir, sólo defino qué métodos debe tener, pero no cómo se implementan, <b>ese trabajo se deja para las clases que quieran adherirse a este contrato "<span style="font-family: "courier new" , "courier" , monospace;">Arrancable</span>"</b>.<br />
<br />
Ahora voy a usar nuestra <b>clase <span style="font-family: "courier new" , "courier" , monospace;">Coche</span> para implementar esta interfaz</b>. Implementar una interfaz quiere decir que debe tener una implementación para cada método que falte por definir. Luego veremos porque digo "los que falten". El código para implementarla es:<br />
<pre><code>public class Coche extends VehiculoConRuedas implements Arrancable {
...
@Override
public void arrancar() {
System.out.println("Coche arrancado");
}
}</code></pre>
<b>Declaramos que se implementa una interfaz con <span style="font-family: "courier new" , "courier" , monospace;">implements</span> y el nombre de la interfaz</b> (o varias separando su nombre por comas). Eclipse va a detectar qué métodos faltan por implementar, y me va a ayudar a escribir el código. Aceptamos la opción "Add all unimplementd methods" que nos da el error o los creamos nosotros. Para terminar nos queda rellenar el cuerpo y habremos acabado.<br />
<br />
Como se puede ver también <b>aparece la anotación <span style="font-family: "courier new" , "courier" , monospace;">@Override</span></b> como ya vimos en <a href="/2019/01/clases-abstractas.html">herencia</a>.<br />
<br />
<h3>
Palabra reservada <span style="font-family: "courier new" , "courier" , monospace;">default</span></h3>
Existe una palabra reservada llamada <b><span style="font-family: "courier new" , "courier" , monospace;">default</span></b> que <b>nos va a servir para declarar una implementación por defecto para un método</b> de interfaz.<br />
<br />
Por otra parte, nuestra clase que implemente una interfaz podría heredar de una clase abstracta que también la implementase parcialmente (recordamos que las clases abstractas no necesitan tener implementados todos sus métodos)<br />
<br />
Por estos dos motivos decía que <b>implementar la interfaz es declarar todos los métodos que faltan</b>, ya que podríamos tener parte del trabajo hecho o incluso todo (la interfaz <span style="font-family: "courier new" , "courier" , monospace;">Serializable</span> no necesita que se implemente ningún método)<br />
<br />
Te propongo que practiques lo siguiente:<br />
<ol>
<li>Comenta la implementación de <span style="font-family: "courier new" , "courier" , monospace;">arrancar()</span> en <span style="font-family: "courier new" , "courier" , monospace;">Coche</span> e implementa <span style="font-family: "courier new" , "courier" , monospace;">Arrancable</span> en <span style="font-family: "courier new" , "courier" , monospace;">VehiculoConRuedas</span><span style="font-family: "courier new" , "courier" , monospace;"></span> con un código distinto al comentado en <span style="font-family: "courier new" , "courier" , monospace;">Coche</span> para <span style="font-family: "courier new" , "courier" , monospace;">arrancar()</span>. Observa que ya no se pide implementar ningún método en <span style="font-family: "courier new" , "courier" , monospace;">Coche</span>.</li>
<li>Descomenta <span style="font-family: "courier new" , "courier" , monospace;">arrancar()</span> en <span style="font-family: "courier new" , "courier" , monospace;">Coche</span>, coméntala en <span style="font-family: "courier new" , "courier" , monospace;">VehiculoConRuedas</span> y añade una implementación <span style="font-family: "courier new" , "courier" , monospace;">default</span> distinta de las anteriores en <span style="font-family: "courier new" , "courier" , monospace;">Arrancable</span>. Ahora créate un objeto <span style="font-family: "courier new" , "courier" , monospace;">Coche</span> y un objeto <span style="font-family: "courier new" , "courier" , monospace;">VehiculoConRuedas</span> e invoca en los dos el método <span style="font-family: "courier new" , "courier" , monospace;">arrancar()</span>. Deben salirte resultados distintos entre ellos <a href="/2019/01/polimorfismo.html">¿Sabes por qué?</a></li>
</ol><p>
Estos ejemplos son muy básicos y podrías pensar que esto mismo lo lograrías definiendo un método <span style="font-family: "courier new" , "courier" , monospace;">arrancar()</span> directamente en <span style="font-family: "courier new" , "courier" , monospace;">VehiculoConRuedas</span>. Tienes razón. Por eso en la siguiente sesión vamos a ver <b>LO IMPORTANTE de las interfaces</b> haciendo que clases completamente distintas acepten el contrato que marca una interfaz.</p>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-24911069088668802202020-05-08T08:22:00.002+02:002022-11-28T14:44:39.416+01:00Despliega tu API en la nube gratisAhora que tenemos nuestra API lista para ejecutarse vamos a ver cómo desplegarla en la nube. Básicamente tenemos que cambiar nuestra BD (ya no nos sirve con H2 en local) y exponer la API públicamente en red. Los servicios que voy a usar para ello son <a href="https://www.elephantsql.com/">ElephantSQL</a> para la BD y <a href="https://www.heroku.com/">Heroku</a> para la API. Para ello vamos a hacer lo siguiente:<br />
<ol>
<li><b>Creo una base de datos</b> en ElephantSQL</li>
<li><b>Cambio el datasource</b> de JPA y el dialecto <code>org.hibernate.dialect.PostgreSQLDialect</code>. Puedes ver qué debes poner en la <a href="https://www.elephantsql.com/docs/java.html">documentación de ElephantSQL para Java</a>.</li>
<li>Una vez cambiado eso voy a <b>cargar los datos de los participantes en la BD</b> ya en la nube. Descomento las líneas del main que lo hacen y lanzo la API. Ya podré ver que todo funciona correctamente pero ahora ya no tengo H2.</li>
<li>Una vez cargada la BD y funcionando toca subir la API.</li>
<li><b>Creo una aplicación en Heroku</b>. Se puede <a href="https://devcenter.heroku.com/articles/deploying-spring-boot-apps-to-heroku">desplegar todo usando el CLI</a>.</li>
<li>Lo más cómodo es <b><a href="https://github.com/LanyuEStudio/Datos-Deportivos-API">enlazar el repositorio de GitHub</a> donde tengamos el código fuente</b> (para que se construya debe tener acceso a todas las dependencias, por eso ya cambiamos la dependencia <code>datos-deportivos</code> a un repositorio). Así incluso podemos tener nuestro despliegue continuo (cada commit se despliega)</li>
<li>Primero tenemos que <b>subir nuestro código</b> con los últimos cambios, <b>pero sin las credenciales de la BD</b> (¡ojo con subir credenciales!)</li>
<li>Una vez enlazado el repositorio ya veremos que podemos desplegar la aplicación. Pero nos faltan <b>las credenciales para conectar con la BD. Vamos a <a href="https://devcenter.heroku.com/articles/deploying-spring-boot-apps-to-heroku#connecting-to-a-database">ponerlas como una variable</a></b> en Heroku para que la use sobrescribiendo lo que haya en el archivo de propiedades.<br />
<pre><code>DATABASE_URL: postgres://usuario:password@server.com:5432/usuario</code></pre>
</li>
<li>Y con esto <b>ya podemos darle a deploy</b>.</li>
</ol><p>
Cuando termine de ejecutarse, si todo ha ido bien, <b>podemos acceder a nuestra API RESTful</b> en la <code>url</code> que nos proporcione Heroku. <b>Todo en la nube, sin coste alguno y sin administrar ninguna máquina</b>.<br />
<br />
Puedes ver el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/0df9bcead567bc48249f37b60eba804dfc1dc8e3">código hasta aquí</a> en su repositorio.</p>
<div class="nota">NOTA: Con el fin de Heroku gratis el 28 de noviembre de 2022, se propone usar como alternativa <a href="https://railway.app">Railway</a>. <br /></div>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-55485714869382130042020-05-07T11:21:00.000+02:002020-05-07T21:36:18.756+02:00Empaquetar la API en un fichero.jarAhora que ya tenemos <a href="https://www.hijosdelspectrum.com/">nuestro MVP desde la entrada anterior</a> vamos a ver cómo empaquetarlo todo. La idea es que podamos ejecutarlo desde cualquier máquina con la JVM.<br />
<br />
Spring Boot nos proporciona una tarea a nuestro proyecto de gradle que se llama <code>bootJar</code> y que es suficiente con ejecutarla. Para ello podemos usar la pestaña "Gradle Tasks" en Eclipse o simplemente <b>ejecutar la tarea <code>bootJar</code> con el mismo gradle wrapper</b> que tenemos en nuestro proyecto. Para esto último usamos desde la carpeta raíz de nuestro proyecto el comando siguiente:<br />
<pre><code>gradlew bootJar</code></pre>
Si todo va bien tendremos nuestro <code>.jar</code> en la carpeta <code>/build/libs</code> de nuestro proyecto:<br />
<br />
Así lo <b>ejecutamos en la consola</b> con el comando:<br />
<pre><code>java -jar datosdeportivosapi-VERSION.jar</code></pre>
Para que se pueda levantar el servicio <b>se necesita tener la BD levantada</b>.<br />
<br />
Para cualquier duda dejarlo en los comentarios.Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com2tag:blogger.com,1999:blog-4756110956436481426.post-84917714672687071812020-05-06T08:36:00.000+02:002020-05-07T11:59:31.910+02:00Código útil: clase ConfiguracionRestComo hemos visto en las entradas anteriores, hemos añadido un link a un método personalizado pero <a href="https://www.hijosdelspectrum.com/2020/05/anadir-link-resourcessearch.html">de una manera muy manual</a> y nos tocaría <a href="https://www.hijosdelspectrum.com/2020/05/rutas-con-pathvariable.html">añadir otro más</a>. No obstante, <b>el código que se ha utilizado puede generalizarse más teniendo en cuenta</b> lo siguiente:<br />
<ol>
<li><b>Los métodos</b> que queremos enlazar <b>tienen la anotación <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ResponseBody.html"><code>@ResponseBody</code></a></b></li>
<li><b>Los parámetros</b> que deben formar parte <b>del path los anotaremos con <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/PathVariable.html"><code>@PathVariable</code></a></b>.</li>
<li><b>Los parámetros</b> que deben formar parte <b>de la query string los anotaremos con <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestParam.html"><code>@RequestParam</code></a></b>.</li>
<li>Podemos <b>configurar a qué recursos queremos añadirles enlaces</b> en su <code>/recursos/search</code> y relacionarlas con su <code>RecursoController</code> que tenga los métodos a los que queremos enlazar.</li>
</ol>
Siguiendo esa guía <b>podemos usar <a href="https://www.oracle.com/technical-resources/articles/java/javareflection.html">Reflection</a> para conseguir toda la información</b> y añadir los enlaces <b>haciendo que</b>:<br />
<ol>
<li><b>Filtre el recurso</b> para el que debe añadirse los links (sólo los registrados)</li>
<li><b>Recupere todos los métodos del <code>Controller</code> asociado</b> a ese recurso</li>
<li><b>Filtre</b> aquellos <b>marcados con <code>ResponseBody</code></b></li>
<li>Recupere el link con <a href="https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/apidocs/org/springframework/hateoas/server/mvc/WebMvcLinkBuilderFactory.html#linkTo-java.lang.Class-java.lang.Object...-"><b><code>linkTo</code> pasando como argumentos</b></a> el valor <code>"(" + nombre + ")"</code> para cada <code>PathVariable</code> (se usan paréntesis para evitar el "escape" de las llaves)</li>
<li>Cambiamos los paréntesis por llaves en las variables de la ruta.</li>
<li>Utilice sólo los nombres de los <b>parámetros <code>RequestParam</code> como query params</b></li>
<li>Use el <b>nombre del método como <code>rel</code></b></li>
</ol>
El código está en el <a href="https://gist.github.com/Awes0meM4n/54ad34cc97914257866993a9be0bcb33">gist para la clase <code>ConfiguracionRest</code></a>:<br />
<br />
También incluye un filtro CORS (<a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/filter/CorsFilter.html"><code>CorsFilter</code></a>) para permitir cualquier petición y así poder realizar pruebas sin preocuparse de las <a href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing">políticas Cross-Origin</a> por defecto.<br />
<br />
<h3>
Cómo usar el bean correctamente</h3>
En nuestro repositorio vamos a eliminar el código de la sesión anterior para sustituirlo por esta clase de configuración que añadimos a nuestra aplicación.<br />
<br />
Ahora <b>personalizamos nuestro bean</b> para los links <b>añadiendo al mapa de controladores para enlazar la entidad</b> como clave (<code>PartidoConId.class</code>) <b>y el controlador asociado</b> como valor (<code>PartidoController.class</code>). De aquí se deduce que lo más simple es poner todos los métodos para una entidad en un único <code>Controller</code>.<br />
<br />
Si se quieren añadir otros enlaces aparte de los aporta este bean, se puede crear otro bean del mismo tipo en otra clase de configuración y al final todos los enlaces convivirán en el mismo <code>/search</code>.<br />
<br />
Puedes encontrar el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/9c205174ec2b1f3921f9bc18c4ccbae315cd7a37">código hasta aquí</a> en su repositorio.<br />
<br />
Este punto finaliza nuestro <a href="https://es.wikipedia.org/wiki/Producto_viable_m%C3%ADnimo">Producto Mínimo Viable</a> (PMV/MVP) y establece la <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/releases/tag/v1.0.0">release v1.0.0</a>. En el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/blob/master/README.md">README</a> del repositorio se pueden ver las instrucciones para ejecutar la API en local.Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-30709923219221362942020-05-05T07:36:00.000+02:002020-05-05T07:36:57.485+02:00Rutas con @PathVariableEn esta entrada vamos a rematar la exposición de nuestros endpoints añadiendo variables a la ruta. Esto es muy normal cuando queremos referirnos a un recurso y nuestras rutas pueden tener fragmentos que sigan el patrón <code>/recursos/:id</code>.<br />
<br />
En el <a href="https://www.hijosdelspectrum.com/2020/05/anadir-link-resourcessearch.html">endpoint expuesto la entrada anterior</a> sólo hemos usado query params. En esta ocasión <b>vamos a pedir los sucesos de un participante entre dos fechas y el <code>id</code> del participante estará en el <code>path</code></b>. La consulta ya está hecha en nuestro repositorio (<code>SucesoDAO</code>), pero no lo estamos exponiendo. Lo hicimos cuando vimos <a href="https://www.hijosdelspectrum.com/2020/04/personalizar-endpoints-con-restresource.html">cómo personalizar rutas con <b><code>@RestResource</code></b></a>. Ésta anotación nos permitía cambiar el fragmento del path que se obtiene en <code>/recursos/search</code> e incluso personalizar los query params con <code>@Param</code>, pero <b>no permite el uso de variables en el <code>path</code></b>. En este ejemplo <b>queremos que el <code>path</code> sea <code>/search/participante/{id}/entre-fechas</code></b>.<br />
<br />
Para esto vamos a tener que seguir usando <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html"><code>@RequestMapping</code></a> o cualquiera de sus variantes en un controlador. Como es una consulta sobre sucesos <b>voy a crearme la clase <code>SucesoController</code></b>. El código se parece muchísimo al que vimos en la última sesión y queda así:<br />
<pre><code>@RepositoryRestController
@RequestMapping(path = "/sucesos/search")
public class SucesoController {
private SucesoDAO sucesoDAO;
SucesoController(SucesoDAO sucesoDAO) {
this.sucesoDAO = sucesoDAO;
}
@GetMapping("/participante/{id}/entre-fechas")
@ResponseBody
public CollectionModel<PersistentEntityResource> getSucesosConIdParticipanteEntreFechas(
@PathVariable("id") String id,
@RequestParam Instant comienzo, @RequestParam Instant fin,
PersistentEntityResourceAssembler assembler) {
List<sucesoconid> sucesos = sucesoDAO.findByIdParticipanteAndTemporalBetween(id, comienzo, fin);
return assembler.toCollectionModel(sucesos);
}
}</sucesoconid></code></pre>
Me voy a centrar en lo nuevo. Si nos fijamos <b>en el <code>path</code> aparece <code>{id}</code>. Cuando ponemos una parte del path entre llaves estamos indicando que se trata de una variable en el <code>path</code></b> y su nombre es el texto que hay dentro. <b>Para hacer referencia a esa variable se usa <code>@PathVariable</code></b> pudiendo anotar un parámetro igual que hicimos con <code>@RequestParam</code>.<br />
<br />
Para poder usarlo desde la ruta <code>/sucesos/search</code> nos <b>toca modificar nuestro bean que procesa el recurso para búsquedas</b> y añadirlo al código que ya tenemos para cuando el tipo gestionado sea <code>SucesoConId</code>. Esto <b>empeora el mantenimiento así que</b>, como lo prometido es deuda, en la siguiente entrada <b>vamos a ver cómo añadir una configuración que nos descubra estos links</b> personalizados automáticamente.<br />
<br />
Puedes conseguir el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/313504300a00ee6ff0c83d21a4abf7dffe8ba298">código hasta aquí</a> en su repositorio.Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-16818620383988732362020-05-04T07:44:00.000+02:002020-05-08T21:07:56.675+02:00Añadir link a /resources/searchPara conseguir en nuestra API REST el nivel 3, HATEOAS, hypermedia o RESTful hace falta que nuestros enlaces se puedan autodescrubrir. Esto no lo digo yo. <b>El padre de REST explica que hypermedia es una condición mínima para llamar a un servicio RESTful</b>. <a href="https://spring.io/guides/tutorials/bookmarks/#_what_makes_something_restful">Una traducción (por google) de lo que ha dicho Roy Fielding al respecto</a>:<br />
<br />
<div class="nota">
<b>Me siento frustrado por la cantidad de personas que llaman a cualquier interfaz basada en HTTP una API REST</b>. El ejemplo de hoy es la API REST de SocialSite. Eso es <a href="https://en.wikipedia.org/wiki/Remote_procedure_call">RPC</a>. Grita RPC. Hay tanto acoplamiento en la pantalla que se le debe dar una calificación X.<br />
<br />
¿Qué se debe hacer para dejar claro el estilo arquitectónico REST sobre la noción de que el hipertexto es una restricción? En otras palabras, <b>si el motor del estado de la aplicación</b> (y, por lo tanto, la API) <b>no está siendo impulsado por hipertexto, entonces no puede ser RESTful y no puede ser una API REST</b>. Período. ¿Hay algún manual roto en algún lugar que deba repararse?
</div>
<br />
Con esto claro, en nuestra API no hay ningún enlace que dirija al método que expusimos en la entrada anterior: no se puede autodescubrir, o conoces la URL o navegando no hay un camino hasta ella. <b>Vamos a añadir un link a nuestro nuevo endpoint para que no se frustre Roy</b>.<br />
<br />
Para ello vamos a usar <b><a href="https://docs.spring.io/spring-data/rest/docs/current/api/org/springframework/data/rest/webmvc/RepositorySearchesResource.html"><code>RepositorySearchesResource</code></a></b>. Éste bean <b>permite añadir links a todos los que se crean automáticamente por Spring Data Rest</b>. Hay varias formas de hacerlo, aunque <a href="https://stackoverflow.com/a/36007306">las que he encontrado por Internet normalmente usan la versión antigua</a> de HATEOAS (<code>ResourceProcessor</code> como veíamos también en la documentación de la entrada anterior). Voy a hacerlo migrado ya a la versión actual y de la forma que me parece mejor por ser más reutilizable.<br />
<br />
Me voy a crear un bean del tipo <code>RepresentationModelProcessor<RepositorySearchesResource></code> en mi clase de configuración. Es decir, <b>voy a indicar el modo en el que se debe procesar el recurso utilizado para las búsquedas</b>. El código quedaría así:<br />
<pre><code>@Bean
RepresentationModelProcessor<RepositorySearchesResource> searchLinks(RepositoryRestConfiguration config) {
return new RepresentationModelProcessor<RepositorySearchesResource>() {
@Override
public RepositorySearchesResource process(RepositorySearchesResource searchResource) {
if (searchResource.getDomainType().equals(PartidoConId.class)) {
try {
String nombreMetodo = "getPartidosConParticipanteComo";
Method method = PartidoController.class.getMethod(nombreMetodo, String.class,
PersistentEntityResourceAssembler.class);
URI uri = org.springframework.hateoas.server.mvc.WebMvcLinkBuilder
.linkTo(method, null, null).toUri();
String url = new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(),
config.getBasePath() + uri.getPath(), uri.getQuery(), uri.getFragment()).toString();
searchResource.add(new Link(url + "{?txt}", nombreMetodo));
} catch (NoSuchMethodException | URISyntaxException e) {
e.printStackTrace();
}
}
return searchResource;
}
};
}</code></pre>
Lo importante de todo este código es que <b>el objeto <code>searchResource</code> se usa para todos los <code>/search</code>. Eso quiere decir que si añadimos un enlace aparecerá en todos ellos</b>. Si queremos añadir el enlace en sólo uno (en nuestro caso para partidos) debemos filtrar para que coincida con el tipo correcto: <code>PartidoConId</code>. Podría ponerse el enlace a mano, pero entonces cualquier cambio posterior nos obligaría a recordar que hay que cambiarlo aquí también.<br />
<br />
<b>Lo mejor es <a href="https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/reference/html/#fundamentals.obtaining-links.builder.methods">recuperar el enlace que apunta a un método</a></b>. Voy a usar <a href="https://docs.spring.io/spring-hateoas/docs/1.0.4.RELEASE/api/org/springframework/hateoas/server/mvc/WebMvcLinkBuilder.html#linkTo-java.lang.reflect.Method-java.lang.Object...-"><code>linkTo</code></a> y le pasaré el método al que quiero apuntar y los parámetros con <code>null</code> (estos son para incluir valores en las variables del path y completar la URL, pero nuestro método sólo tiene query params).<br />
<br />
<div class="nota">
NOTA: Para usar <code>linkTo</code> se puede usar la importación estática para evitar poner su paquete, pero lo he dejado en el código para que quede claro de dónde viene el método.
</div>
<br />
Lamentablemente, <a href="https://github.com/spring-projects/spring-hateoas-examples/tree/master/spring-hateoas-and-spring-data-rest#altering-what-spring-data-rest-is-serving"><b>el link no incluye el <code>basePath</code></b> y hay que añadírselo</a> (como dicen ellos: "por ahora") por eso se construye de nuevo la URI incluyéndolo. Para recuperar ese valor lo mejor es usar <code>RepositoryRestConfiguration</code> que puede ser inyectada en el método.<br />
<br />
Ya con la información completa <b>podemos añadir el enlace en <code>searchResource</code> incluyendo los query params</b>.<br />
<br />
Si ejecutamos veremos que nuestro enlace aparece al final de los que se han añadido automáticamente.<br />
<br />
No obstante, supongo que a cualquiera le dolerá ver esos "magic number" y pensará que <b>debe haber una forma más automática de hacerlo</b>. Personalmente no la he encontrado y tiene sentido (<a href="https://stackoverflow.com/a/25217113">no se puede saber cómo se van a añadir los endpoints</a> porque hay varias formas). Si alguno encuentra algo oficial agradecería que lo compartiera en los comentarios para completar la entrada.<br />
<br />
Además en la siguiente entrada voy a añadir otro endpoint usando una variable en el path y también habrá que añadir el link. <br />
<br />
<b>Para arreglar esta falta de automatización, después de la siguiente entrada voy a compartir una clase de configuración base que puede utilizarse de manera generalizada en los proyectos</b> igual que hicimos con el <code>jpa-config.xml</code> y explicaré la forma en que debe usarse. Así veremos que sólo con eso detecta y añade este endpoint y el que veamos a continuación que incluye path variable (y es otro parámetro que debemos controlar).<br />
<br />
Puedes obtener el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/d8d8b9aab80dcb8b3f860fbd335de7b95d05b3cc">código hasta aquí</a> en su repositorio. Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-70939406554068491532020-04-30T08:19:00.000+02:002020-04-30T18:03:13.791+02:00Exponer metodo con @RepositoryRestControllerEn la entrada anterior hemos añadido un método personalizado a nuestro repositorio para una consulta más compleja que las que nos resuelve JPA en automático. Incluso luego la hemos optimizado utilizando datos cacheados en memoria.<br />
<br />
Pero <b>para que los usuarios de la API puedan utilizarlo hace falta exponerla</b>. Esto <b>no es automático</b>, si revisamos los links generados por HATEOAS no aparecerá. La funcionalidad <b>está en la capa de persistencia</b> pero no es parte de la interfaz de la API.<br />
<br />
Lo tradicional es tener un <code>@Controller</code> donde se indica el <code>@RequestMapping</code> que corresponda para conducir la llamada HTTP hasta el método concreto y derivar a un <code>@Service</code>. Más adelante veremos con más detalle y compararemos esta forma con la que vamos a usar aquí. Ahora quiero centrarme en la forma de hacerlo con Data Rest.<br />
<br />
En esta entrada se va a <b>utilizar la anotación <a href="https://docs.spring.io/spring-data/rest/docs/current/api/org/springframework/data/rest/webmvc/RepositoryRestController.html">@RepositoryRestController</a> para continuar dentro del módulo Data Rest</b>. El javadoc lo deja muy claro:<br />
<br />
<div class="nota" style="font-size: 1em;">
Anotación para marcar los controladores Spring MVC proporcionados por Spring Data REST. Permite detectarlos fácilmente y <b>excluirlos del manejo estándar de Spring MVC</b>.<br />
Esta anotación sólo debe ser <b>utilizada por los controladores de aplicaciones que se asignan a los URI administrados por Spring Data REST</b>, ya que los maneja una implementación especial <b>que aplica funcionalidad adicional</b>.
</div>
<br />
Esto la convierte en el camino correcto y <a href="https://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.overriding-sdr-response-handlers">así lo indica la documentación</a>. Lamentablemente la documentación no está actualizada y sigue utilizando el HATEOAS antiguo (<code><a href="https://docs.spring.io/spring-hateoas/docs/0.24.0.RELEASE/api/org/springframework/hateoas/Resource.html">Resource</a>/s</code>) así que aquí <a href="https://docs.spring.io/spring-hateoas/docs/1.0.0.M1/reference/html/#migrate-to-1.0.changes.representation-models">se va a ver cómo hacerlo migrado ya a la versión actual</a>.<br />
<br />
Me creo una clase <code>PartidoController</code> en mi paquete de <code>rest</code> con el siguiente código:<br />
<pre><code>@RepositoryRestController
public class PartidoController {
private PartidoDAO partidoDAO;
PartidoController(PartidoDAO partidoDAO) {
this.partidoDAO = partidoDAO;
}
@GetMapping("/partidos/search/con-nombre-participante")
@ResponseBody
public CollectionModel<PersistentEntityResource> getPartidosConParticipanteComo(@RequestParam String txt,
PersistentEntityResourceAssembler assembler) {
List<PartidoConId> partidos = partidoDAO.getEventosConParticipanteConTexto(txt);
return assembler.toCollectionModel(partidos);
}
}</code></pre>
Al marcar la clase se excluyen de la gestión de Spring MVC (la que usa <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RestController.html"><code>@RestController</code></a> por ejemplo) y pasan a ser gestionados por Data Rest. Con ello se consigue:<br />
<ol>
<li>El controlador <b>utilizará la configuración <code>basePath</code></b> de Data Rest (en nuestro caso <code>/api</code>)</li>
<li>Se podrá <b>inyectar en sus métodos el objeto <a href="https://docs.spring.io/spring-data/rest/docs/current/api/org/springframework/data/rest/webmvc/PersistentEntityResourceAssembler.html"><code>PersistentEntityResourceAssembler</code></a></b>. Este objeto <b>va a permitir la creación de <a href="https://docs.spring.io/spring-hateoas/docs/current/reference/html/#fundamentals.resources"><code>EntityModel</code> o <code>CollectionModel</code></a></b> (lo que sustituye a <code>Resource/s</code>) en función de si hay que representar un elemento o una colección de elementos. Así <b>la representación será la misma</b> que la que se genera para el resto de métodos, <b>dando consistencia a la API</b>.</li>
</ol>
Hay otras dos anotaciones necesarias según se ve en la documentación:<br />
<ul>
<li><b><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/GetMapping.html"><code>@GetMapping</code></a></b>: usamos esta anotación que equivale a <code><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html">@RequestMapping</a>(method=GET,...)</code>. Ambas sirven <b>para indicar el endpoint</b> que dirigirá hasta el método. La ruta la ponemos sin el <code>basePath</code>. Si se pone <b>a nivel de clase el fragmento afecta a todos los mapeos</b> internos.</li>
<li><b><a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ResponseBody.html"><code>@ResponseBody</code></a></b>: significa que <b>el método devuelve una respuesta web</b>. Si no se pone no se expondrá el endpoint. También <b>se puede poner a nivel de clase</b> para que todos los métodos de dentro tomen ese valor por defecto.</li>
</ul>
<br />
Ahora al arrancar la API podemos probar ese endpoint y ver que existe y funciona correctamente. Sin embargo si acudimos a <code>/partidos/search</code> no se va a mostrar y no se puede autodescubrir.<b> En la próxima entrada vamos a ver cómo añadir este nuevo endpoint a los link de <code>/partidos/search</code></b>.<br />
<br />
Puedes encontrar el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/c6d935eff93e4e4c42cd9195949ee8da7496ad5b">código hasta aquí</a> en su repositorio.Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-64315122187537760092020-04-29T09:37:00.000+02:002020-04-30T08:21:37.277+02:00Añadir método personalizado en repositorioYa conocemos lo básico de Spring Data Rest y cómo nos facilita exponer los recursos que tenemos almacenados en nuestra BD con JPA. Sin embargo todos los métodos que hemos usado hasta ahora tienen una relación directa con las columnas de la BD (por ejemplo busco <code>Participante</code>s usando su campo <code>nombre</code>) y en nuestro proyecto seguramente habrá aspectos que no podamos solucionar con lo visto hasta ahora. Como ejemplo nos gustaría recuperar los <code>Partido</code>s de un <code>Participante</code> con un texto en su <code>nombre</code>. En este caso tenemos un problema: en la tabla <code>PARTIDOS</code> no están los nombres de los participantes, sólo sus ids y <b>ya no es suficiente con usar los <a href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods">Query Methods de JPA</a> para solucionarlo</b>.<br />
<br />
En este caso <b>necesitamos hacer un método personalizado para nuestro repositorio</b>.<br />
<br />
Para implementar esta funcionalidad vamos a hacer lo siguiente:<br />
<ol>
<li>Crear una búsqueda para <b>buscar los participantes</b> conteniendo un texto (eso ya lo tenemos: <code>ParticipanteDAO.findByNombreIgnoreCaseContaining</code>)</li>
<li>Con los participantes recuperados <b>buscaremos los partidos en los que aparecen</b></li>
<li><b>Añadiremos todos los partidos</b> de esos participantes <b>a una lista</b></li>
<li><b>Esa lista será el payload de nuestra respuesta HTTP</b></li>
</ol>
Para añadir un método personalizado <a href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.custom-implementations"><b>Data Rest permite que nuestro DAO se complete usando fragmentos</b> implementados de varias interfaces</a>, además de la que estamos usando <code>JpaRepository</code>. Esas interfaces tendrán los métodos que queremos añadir. En nuestro caso me creo la interface <code>EventoDAOCustom</code>:<br />
<pre><code>public interface EventoDAOCustom<T extends Evento> {
List<T> getEventosConParticipanteConTexto(String txt);
}</code></pre>
De esta forma esta interface me servirá para otro tipo de eventos, no sólo para partidos.<br />
Ahora me <b>creo una clase que implemente este método</b>. Lo más importante es que <b>debe tener el sufijo "<code>Impl</code>" e implementar nuestra nueva interface</b>. Nos quedará así:<br />
<pre><code>class PartidoDAOImpl implements EventoDAOCustom<PartidoConId> {
@Autowired
ParticipanteDAO participanteDAO;
@PersistenceContext
EntityManager entityManager;
@Override
public List<PartidoConId> getEventosConParticipanteConTexto(String txt) {
List<Participante> participantes = participanteDAO.findByNombreIgnoreCaseContaining(txt);
Set<PartidoConId> partidos = new HashSet<PartidoConId>();
Query query = entityManager.createNativeQuery(
"SELECT p.* FROM partidos as p " +
"WHERE p.local = ?1 OR p.visitante = ?1", PartidoConId.class);
participantes.forEach(p -> {
query.setParameter(1, p.getIdentificador());
partidos.addAll(query.getResultList());
});
return new ArrayList<PartidoConId>(partidos);
}
}</code></pre>
Para implementar este método usamos <code>ParticipanteDAO</code> para recuperar los participantes y luego una <a href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query"><code>Query</code></a> que es bastante parecida a lenguaje SQL que ya conocemos como se ve en el ejemplo.<br />
<br />
<div class="nota">
NOTA: la query necesita del <a href="https://docs.oracle.com/javaee/7/api/javax/persistence/EntityManager.html"><code>EntityManager</code></a> y lo inyectamos con <code>@PersistenceContext</code>.
</div>
<br />
Usamos la query para ir llamando a nuestra BD con cada participante que cumpla con el texto pasado y lo añadimos a un <code>Set</code> para que no se repita. Finalmente devolvemos todos los partidos. Podrían hacerse más cosas como ordenar por algún campo, etc...<br />
<br />
Sólo nos queda añadir la interface a nuestro DAO para que Spring lo complete: <br />
<pre><code>public interface PartidoDAO extends JpaRepository<PartidoConId, Long>, EventoDAOCustom<PartidoConId> {...}</code></pre>
<br />
<b>Lo importante de todo esto es que puedo utilizar código propio</b> para implementar aquello que ya se escapa de los Query Methods de JPA. Sin necesidad de añadirlo como bean ni nada parecido, Data Rest va a escanear las clases con el sufijo <code>Impl</code> (se puede configurar) buscando la implementación de <code>EventoDAOCustom</code> que no puede hacer automáticamente. <b>Así irá formando con fragmentos de código el DAO completo</b>.<br />
<br />
Ahora probamos que todo funciona correctamente en nuestro <code>main</code>:<br />
<pre><code>partidoDAO.getEventosConParticipanteConTexto("m").stream()
.map(PartidoConId::toString)
.forEach(log::trace);</code></pre>
Para que esto funcione de esta forma <b>debemos indicar que este método es <a href="https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#transactions">transaccional</a></b>. Como habitualmente estas consultas personalizadas son lecturas de datos, marcamos toda la clase como sólo lectura.<br />
<pre><code>@Transactional(readOnly = true)
class PartidoDAOImpl implements EventoDAOCustom<PartidoConId> {...}</code></pre>
<div class="nota">
NOTA: ver más sobre cómo funciona <a href="https://dzone.com/articles/how-does-spring-transactional"><code>@PersistentContext</code> y <code>@Transactional</code></a>.
</div>
<br />
Puedes conseguir el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/c2d5fd5ed3c7609dcf4ace808781aff9c31bf099">código hasta aquí</a> en su repositorio.<br />
<br />
Añadida la funcionalidad a nuestra persistencia, lo que nos falta es exponerla en nuestra API. En la siguiente entrada vamos a ver cómo hacerlo.<br />
<br />
<b>Práctica: mejorar cómo se consiguen los participantes</b><br />
Ahora mismo si buscamos un equipo donde su nombre incluya una tilde en el fragmento buscado y no coincida en el valor de <code>txt</code> (ejemplo: buscando "<code>?txt=atletico</code>" no devolverá "Atlético...") parece que no obtenemos el resultado esperado. Se pide modificar el código para que no sólo sea <code>IgnoreCase</code> sino que también ignore las tildes.<br />
<br />
<div class="nota">
NOTA: aparte de <code>ParticipanteDAO</code> tenemos otro origen de datos disponible para ello en forma de bean. Para facilitar el tratamiento de las tildes existe un método en <code>lanyu.commons</code> que se encarga de ello: <code>StringUtils.eliminarTildes</code>.
</div>
<br />
La <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/352e7a2cc867cae31054a40d54fc9e607b4c0341">solución</a> está en github.
Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-50398648128746657522020-04-28T09:19:00.001+02:002020-04-28T09:19:06.931+02:00Personalizar endpoints con @RestResourceEn estos momentos tenemos un servicio HATEOAS que puede ser descubierto navegando por sus enlaces desde el raíz. Sin embargo sólo hemos configurado las propiedades de <code>@RepositoryRestResource</code>. En nuestras aplicación probablemente querremos poder personalizar alguna URL (sobretodo de los métodos que nos hacemos con query methods de JPA), no permitir algunas operaciones (por ejemplo no dejar borrar un participante) o al menos no permitirlas para todos los usuarios (sólo podrá borrar el adminitrador).<br />
<br />
Vamos a ver <b>cómo personalizar algunos de estos aspectos utilizando la anotación <a href="https://docs.spring.io/spring-data/rest/docs/current/api/org/springframework/data/rest/core/annotation/RestResource.html"><code>@RestResource</code></a></b>.<br />
<br />
En <code>ParticipanteDAO</code> voy poner las siguientes líneas:<br />
<pre><code>@RestResource(path="nombre")
List<Participante> findByNombreIgnoreCaseContaining(String txt);
@RestResource(exported=false)
void deleteById(String id);
@RestResource(exported=false)
void delete(Participante entity);
// Mejor ponerselo a todo lo que tenga que aplicarse
// void deleteAll(Iterable<? extends Participante> entities);
// ...</code></pre>
Estas líneas hacen dos cosas:<br />
<ol>
<li>Modifica la URL para que el fragmento del <code>path</code> del primer método sea <code>nombre</code>.</li>
<li>Prohibe el uso de <code>DELETE</code> sobre <code>Participante</code>s.</li>
</ol>
<div class="nota">
NOTA: <code>JpaRepository</code> tiene 6 distintos métodos de borrado, formalmente y por rendimiento es mejor aplicar la anotación a todos ellos aunque con uno sólo sería suficiente. Aquí se indica pero no se ha hecho para ahorrar código.
</div>
<br />
Ahora quiero buscar un <code>Partido</code> donde participe un <code>idParticipante</code> que le pase. Pongo esta línea en <code>PartidoDAO</code>:<br />
<pre><code>@RestResource(path="participante")
List<PartidoConId> findByIdLocalOrIdVisitante(@Param("idParticipante") String idLocal, @Param("idParticipante") String idVisitante);</code></pre>
Aquí utilizo además la anotación <a href="https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/query/Param.html"><code>@Param</code></a>. Esta anotación me permite enlazar un <a href="https://en.wikipedia.org/wiki/Query_string">query parameter</a> de la petición HTTP a un parámetro de mi método. La sintaxis del <b>query method de JPA me va a obligar a tener tantos parámetros como vea que necesita</b> su nombre (<code>idLocal</code> e <code>idVisitante</code>). Sin embargo puedo enlazar el mismo query parameter a los dos parámetros.<br />
<br />
Puedo encontrar estos <b>enlaces en el path <code>/search</code> de cada recurso</b>.<br />
<br />
Puedes encontrar el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/16fb731fbfc6342de03914c0db5238e7e2c72095">código hasta aquí</a> en su repositorio.<br />
<br />
Esta entrada es muy corta pero <b>ya se puede practicar</b> mucho con ella. Propongo hacer los siguientes métodos de búsqueda y ponerles el nombre apropiado:<br />
<ol>
<li>Buscar sucesos por <code>id</code> de <code>Participante</code></li>
<li>Buscar sucesos entre un <code>Instant</code> <code>comienzo</code> y otro <code>fin</code></li>
<li>Buscar sucesos para un <code>idParticipante</code> entre <code>comienzo</code> y <code>fin</code>. Este método no se expondrá en la API.</li>
<li>Buscar sucesos después de un <code>Instant instant</code>. El query param tendrá el nombre <code>start</code>.</li>
</ol>
La <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/d4141ffdc33be9a4938831128529d01ffacc0fae">solución</a> está en github.Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-61814792184798093232020-04-15T11:50:00.002+02:002021-05-02T17:05:15.187+02:00Inyectar un bean a un RestResource<b>Seguimos con el Paso 3</b> de la entrada anterior. Ya tenemos nuestra bean <code>ServicioEntidad</code> cargada e inyectándose en las entidades (Sucesos) que se leen de la BD. <b>¿Qué pasa con los objetos que se crean al deserializarse el JSON de la llamada HTTP? Esos objetos también deben ser inyectados con el <code>ServicioEntidad</code></b>, no podemos esperar a que sean leídos desde la BD pues podría fallar el proceso que necesitara esa llamada.<br />
<br />
Ahora nuestro punto de entrada es el controlador que recibe esa llamada. Si lo hicieramos a mano podríamos gestionarlo en el <code>@Controller</code>, cuando Jackson lo convirtiera y se lo pasara como argumento a nuestro método del controlador. <b>Sin embargo nosotros usamos Data Rest</b>, pero <b>podemos inyectarlo</b> justo antes, <b>personalizando el bean que se encarga de leer ese JSON</b> y convertirlo en nuestro objeto de negocio (en el ejemplo <code>SucesoConId</code> o sus subtipos). Para ello <b>vamos a crear un bean que al deserializar el JSON inyecte nuestro bean <code>ServicioEntidad</code></b>. Para esto <b>jackson nos da una anotación perfecta: <code>@JsonComponent</code></b>.<br />
<br />
La anotación <a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/jackson/JsonComponent.html"><code>@JsonComponent</code></a> se puede <a href="https://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-json-components">usar sobre una clase que implemente (de)serialización o sobre una clase que contenga varias implementaciones internas</a> que cargará todas. Hay <a href="https://www.baeldung.com/spring-boot-jsoncomponent">varias formas</a> de aprovecharlo, nosotros <b>nos vamos a centrar en crear en una clase todas las implementaciones de deserializador</b> que necesitamos para nuestros distintos sucesos usando <a href="https://fasterxml.github.io/jackson-databind/javadoc/2.7/com/fasterxml/jackson/databind/deser/std/StdDeserializer.html"><code>StdDeserializer</code></a>.<br />
<br />
Ya que vamos a hacernos un serializador para cada tipo de suceso vamos a aprovechar la herencia para reutilizar código. El esquema general quedará:<br />
<pre><code>@JsonComponent // Registra la clase/clases internas con (de)serializadores
public class JsonDeserializers {
public static class JsonSucesoSerializer<T extends SucesoConId> extends StdDeserializer<T> {... }
public static class JsonGolSerializer extends JsonSucesoSerializer<GolConId> {...}
public static class JsonTarjetaSerializer extends JsonSucesoSerializer<TarjetaConId> {...}
}</code></pre>
<div class="nota">
NOTA: los deserializadores de gol y tarjeta van a heredar del serializador de suceso.
</div>
<br />
Ya vimos un ejemplo en clase haciendo un deserializador personalizado. En este caso lo vamos a repetir pues se trata de ir leyendo el JSON e ir asignando los valores en cada campo. Para ello vamos a tener que añadir algún setter más a los <code>Sucesos</code> si no queremos hacer uso de Reflection. <b>Lo interesante va a ser el uso de genéricos</b> para que se adapten al resto de implementaciones para <code>GolConId</code> y <code>TarjetaConId</code> <b>y el uso de un callback</b> para poder añadir lo que falte en tipos que lo necesitan (añadir el tipo de tarjeta en <code>TarjetaConId</code> por ejemplo). La implementación de <code>JsonSucesoSerializer<T extends SucesoConId></code> resumida quedará así:<br />
<pre><code>public static class JsonSucesoSerializer<T extends SucesoConId> extends StdDeserializer<T> {
// Constructores heredados del padre StdDeserializer<T>
@Override
public T deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException, JsonProcessingException {
return deserializarSuceso(jsonParser, (Class<T>) handledType());
}
protected T deserializarSuceso(JsonParser jsonParser, Class<T> tipo)
throws IOException, JsonProcessingException {
T sucesoDeserializado;
// Construir y asignar valores a campos. Ver a continuacion.
postDeserializarSuceso(nodo, sucesoDeserializado);
return sucesoDeserializado;
}
// Callback para completar la deserializacion
protected T postDeserializarSuceso(JsonNode nodo, T sucesoDeserializado) {
return sucesoDeserializado;
}
}</code></pre>
Lo más importante de implementar <code>StdDeserializer</code>, aparte del parseo en sí, se puede decir que es <b>el método <code>handledType()</code>. Usando ese método vamos a tener acceso al tipo de objeto que hay que deserializar</b> y con ello podremos tener acceso al constructor que necesitemos. Para eso sí que vamos a usar <code>Class</code>. Con estas herramientas la construcción de un <code>SucesoConId</code> quedará:<br />
<pre><code>protected T deserializarSuceso(JsonParser jsonParser, Class<T> tipo)
throws IOException, JsonProcessingException {
T sucesoDeserializado;
try {
// Variable intermedia final para los lambdas
// El constructor vendra marcado por el tipo a construir y el parametro indicado
T suceso = tipo.getConstructor(ServicioEntidad.class).newInstance(servicioEntidad);
JsonNode nodo = jsonParser.getCodec().readTree(jsonParser);
// Podemos usar Optional para seguir ejecutando
// o no si no hay campo
Optional.ofNullable(nodo.get("timestamp")).ifPresent(n -> suceso.setTimestamp(n.asLong()));
Optional.ofNullable(nodo.get("idParticipante")).ifPresent(n -> suceso.setIdParticipante(n.asText()));
Optional.ofNullable(nodo.get("partido")).ifPresent(n -> {
String idPartido = n.asText();
idPartido = idPartido.substring(idPartido.lastIndexOf("/") + 1, idPartido.length());
suceso.setPartido(partidoDAO.getOne(Long.parseLong(idPartido)));
});
postDeserializarSuceso(nodo, suceso);
sucesoDeserializado = suceso;
} catch (Exception e) {
e.printStackTrace();
sucesoDeserializado = null;
}
return sucesoDeserializado;
}</code></pre>
Lo destacable de aquí es que este método <b>recibe el tipo del método <code>handledType()</code></b> que será implementado distinto en sus clases hijas. Con el tipo se construye con el constructor que admite un <code>ServicioEntidad</code> y ya estrará inyectado. Lo demás es ir leyendo y asignando valores hasta la invocación de <code>postDeserializarSuceso(nodo, suceso)</code>. <b>Con este callback dejamos la puerta abierta</b> a completar la deserialización en las clases hijas.<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="420" mozallowfullscreen="" scrolling="no" src="https://slides.com/lanyu/deck/embed#/0/2" webkitallowfullscreen="" width="576"></iframe>
<br />
Ahora que ya hemos completado el serializador padre podemos aprovecharlo para rematar con los otros dos que necesitamos:<br />
<pre><code>// Basta con devolver el tipo a construir
public static class JsonGolSerializer extends JsonSucesoSerializer<GolConId> {
@Override public Class<?> handledType() { return GolConId.class; }
}
public static class JsonTarjetaSerializer extends JsonSucesoSerializer<TarjetaConId> {
@Override public Class<?> handledType() { return TarjetaConId.class; }
@Override
protected TarjetaConId postDeserializarSuceso(JsonNode nodo, TarjetaConId sucesoDeserializado) {
Optional.ofNullable(nodo.get("tipoTarjeta"))
.ifPresent(n -> sucesoDeserializado.setTipoTarjeta(TipoTarjeta.valueOf(n.asText())));
return sucesoDeserializado;
}
}</code></pre>
Vemos que nos quedan muy simples: para <code>Gol</code> sólo necesita decir el tipo que maneja y para <code>Tarjeta</code> también utilizar el callback para asignar el tipo de tarjeta (amarilla o roja) que traiga el nodo.<br />
<br />
Ahora ya nos pueden hacer llamadas HTTP que Jackson se encargará de inyectar el <code>ServicioEntidad</code>.<br />
<br />
Puedes conseguir el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/d6c6b334206669df230b0e92532ed1cd1b88f1bb">código hasta aquí</a> en su repositorio.<br />
<br />
Como práctica se propone hacer lo mismo para los partidos, ya que si se invocase código que necesitase del <code>ServicioEntidad</code> en la clase <code>Partido</code> nuestro código fallaría si no lo ha cargado ya <code>@PostLoad</code> en la práctica que vimos en la entrada anterior. Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-16148873368025253542020-04-14T08:53:00.001+02:002021-05-02T17:03:20.121+02:00Inyectar un bean a una Entidad leída desde BDCon lo que estamos viendo, lo normal es guardar y recuperar datos de una BD usando JPA. También hemos visto la flexibilidad que nos proporciona trabajar con inyección de dependencias. <b>La inyección se produce para los beans y sin embargo nuestros "objetos de negocio" no lo son y no se puede hacer <code>Autowired</code> sobre ellos</b> (me refiero a los objetos que se recuperan de una BD o los que nos llegan en formato JSON por una petición HTTP y deserializa nuestro <code>ObjectMapper</code>).<br />
<br />
¿Qué importancia tienes esto?<br />
<br />
Nuestra tabla <b>Participantes tiene pocos datos y se modificará poco</b> (los participantes podrán ser modificados como mucho cada comienzo de liga). Aquí tendría sentido tener todos esos datos cargados (o al menos un subconjunto como <code>id</code> y <code>nombre</code>) en memoria porque sin embargo van a ser consultados muy a menudo (clasificación, enfrentamientos históricos, jornada actual, etc...). La BD cuenta con una cache para todos los datos, pero en particular <b>estos valores darán mejor rendimiento si los tenemos en memoria</b> (incluso se podrían leer de un archivo sin necesidad de la BD). Esto <b>lo vamos a hacer creando un bean del tipo <code>ServicioEntidad</code></b> que no es más que una interface que puede almacenar objetos ordenados por clase y por <code>id</code> usando mapas/diccionarios.<br />
<br />
<div class="cita cita-warning">
<div class="cita-texto">
<code>@Autowired</code> sólo funciona en los beans. Nuestras entidades no lo son.
</div>
<br class="clearBoth" /></div>
<br />
Nuestra librería de <a href="https://github.com/LanyuEStudio/datos-deportivos">datos-deportivos</a> tiene un campo <code>ServicioEntidad</code> en los <b>tipos <code>Partido</code> y <code>SucesoImpl</code> y necesitan un objeto que implemente esa interfaz para hacer el "lazy loading" de participantes</b> (sólo se recupera de la BD el <code>idParticipante</code>, pero no se referencia al objeto hasta que no se tiene que usar). <b>Ni Partidos ni Sucesos son beans</b> gestionados por el contenedor así que <b>¿Cómo puedo inyectar en ellos</b> tanto por el lado web como por la BD el bean <code>ServicioEntidad</code>? Para ello voy a tener la <b>ayuda de los callbacks, los listeners y los deserializadores</b> personalizados.<br />
<br />
He dividido esta sesión también en varios pasos:<br />
<ul>
<li>Paso 1: Creo mi bean <code>ServicioEntidad</code> y modifico mis sucesos para poder darles este valor a su campo. Este código no tiene ningún misterio y empezaremos desde este punto.</li>
<li>Paso 2: <b>Inyecto el bean <code>ServicioEntidad</code> en los sucesos cuando los lea desde la BD</b>. Ésta es la finalidad de esta entrada.</li>
<li>Paso 3: La otra vía de entrada es desde mi API REST. Los objetos json vendrán sin un <code>ServicioEntidad</code> que tendré que inyectar. Este paso lo haré en la siguiente entrada para no alargarme, pero on quería desvincularlo de esta funcionalidad de inyectar un bean.</li>
</ul>
<br />
Para explicar lo que voy a hacer voy a servirme de la siguiente figura:<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="420" mozallowfullscreen="" scrolling="no" src="https://slides.com/lanyu/deck/embed#/0/1" webkitallowfullscreen="" width="576"></iframe>
Lo que representa la figura son los <b>caminos de entrada y salida de nuestros objetos de negocio</b> en Java teniendo nuestra aplicación en el centro y por fuera:<br />
<ol>
<li><b>Las operaciones con la BD</b>: Las entidades tienen un ciclo de vida con una serie de eventos</li>
<li><b>Las llamadas HTTP</b> que nos llegarán a la API: vimos que la salida la modificabamos con MixIns, ahora toca configurar la entrada </li>
</ol>
<br />
<h3>
Ciclo de vida de un Entity</h3>
<b>Las entidades pasan por un ciclo de vida con los <a href="https://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Events">eventos/callbacks</a> <code>PostLoad</code>, <code>PrePersist</code>, <code>PostPersist</code>, <code>PreUpdate</code>, <code>PostUpdate</code>, <code>PreRemove</code> o <code>PostRemove</code> en función de las operaciones que se ejecuten en la BD</b>. Básicamente son eventos anteriores y posteriores a las operaciones guardar, actualizar y borrar y luego otro después de cargarse en el <code>entityManager</code>.<br />
<br />
<div class="nota">
NOTA: Estos eventos son de JPA. <a href="https://docs.spring.io/spring-data/rest/docs/current/reference/html/#events">Data Rest tiene otros eventos</a> y hay otros muchos de otras librerías. En el Paso 3 implementaremos uno y lo veremos con más detalle. Lo bueno de estos eventos es que te servirán para gestionar todas las entidades cuando uses JPA.
</div>
<br />
Para lo que queremos hacer <b>vamos a usar el evento <code>@PostLoad</code></b>. Así después de leer un <code>SucesoConId</code> voy a asignarle el <code>ServicioEntidad</code> que he creado en mi contenedor Spring.<br />
<br />
<div class="nota">
NOTA: Mirando la documentación puede parecer que la anotación <a href="https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/annotation/PersistenceConstructor.html"><code>@PersistenceConstructor</code></a> es exactamente lo que necesitamos. <a href="https://jira.spring.io/browse/DATAJPA-37">Esta anotación es de Spring pero <b>JPA en la actualidad obliga a tener un constructor sin parámetros</b></a>. De hecho si lo quitais Hibernate os dará ese error y, <a href="https://stackoverflow.com/a/29433238/2443951">aunque Hibernate pueda contruir una entidad sin un constructor sin parámetros</a>, JPA que es la API que estamos usando si lo necesita.
</div>
<br />
<b>Voy a crearme una clase llamada <code>SucesoListener</code> que será un bean</b> y lo voy a cargar con <code>@Component</code>. JPA no obliga a implementar ninguna interface así que esta clase no heredará de nadie pero va a tener <b>un método que será anotado con <code>@PostLoad</code></b>. Esto indicará que ese método debe ejecutarse justo después de leer un <code>SucesoConId</code> (del tipo que sea) desde la BD. Para poder <b>modificar ese suceso leído me llega por el parámetro</b> de ese método con un simple <code>setServicioEntidad(servicioEntidad)</code> que añado a <code>SucesoConId</code> (y de ahí heredarán el resto). Sólo falta recuperar <b>el bean <code>ServicioEntidad</code> de mi contenedor que puedo inyectárselo al Listener porque también lo gestiona Spring</b>. Mi Listener queda así:<br />
<pre><code>@Component
public class SucesoListener {
private static ServicioEntidad servicioEntidad;
@Autowired
public void init(ServicioEntidad servicioEntidad) {
SucesoListener.servicioEntidad = servicioEntidad;
}
@PostLoad
void setServicioEntidadEnSuceso(SucesoConId suceso) {
suceso.setServicioEntidad(servicioEntidad);
}
}</code></pre>
Queda <b>relacionar esta clase con la entidad a la que debe suscribirse</b> para recibir el evento. Con una simple anotación en la clase <code>SucesoConId</code> estará hecho:<br />
<pre><code>@EntityListeners(SucesoListener.class)
public class SucesoConId extends SucesoImpl { ... }</code></pre>
Y <b>cargar el bean <code>SucesoListener</code> en el contenedor</b> como cualquier otro <code>@Component</code> escaneando su paquete (usamos <code>"es.lanyu.eventos"</code> que servirá también para el <code>@JsonComponent</code> del paso 3).<br />
<pre><code>@ComponentScan("es.lanyu.eventos") // Tambien para registrar JsonSerializers
public class ConfiguracionPorJava {...}</code></pre>
Si imprimimos nuestros partidos por consola veremos que ya aparecen enriquecidos con el <code>toString()</code> de <code>Participante</code> con lo que <b>nuestro <code>ServicioEntidad</code> ha sido inyectado en cada suceso</b> y está recuperando correctamente nuestros participantes.<br />
<br />
<div class="codigo">
SucesoConId #35, participante: <span data-darkreader-inline-color="" style="--darkreader-inline-color: #33ff33; color: lime;">Oviedo</span> OVIEDO(ESP) fecha=2020-04-13T17:25:53.297Z<br />
Gol para el <span data-darkreader-inline-color="" style="--darkreader-inline-color: #33ff33; color: lime;">Oviedo</span> OVIEDO(ESP)<br />
Tarjeta AMARILLA <span data-darkreader-inline-color="" style="--darkreader-inline-color: #33ff33; color: lime;">Heerenveen</span> HERENV(HOL)
</div>
<br />
Puedes ver el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/1aa9db01c3bf7f0a8b8bbb5e10a6111ba592d943">código hasta aquí</a> en su repositorio. En la siguiente entrada <b>continuamos con el Paso 3</b>, pero no olvides hacer la práctica que te pongo más abajo.<br />
<br />
Para asimilar este contenido dejo una <b>práctica</b> en la que habrá que utilizar lo que hemos visto:<br />
<br />
Se pide <b>implementar lo necesario para <code>PartidoConId</code> el <code>toString()</code> que muestre el <code>id</code> y los <code>detallesDelPartido()</code></b> (ya implementado y comentado en la última línea del <code>toString()</code> pero habrá que inyectar <code>ServicioEntidad</code>). Además si ahora se intenta usar la API REST para hacer un <code>DELETE</code> sobre un partido con sucesos ocurrirá un error 500: <b>implementar lo necesario para poder borrar un partido con sucesos</b> (también se deben borrar antes sus sucesos). Como ya tenemos cierto nivel con Spring se pide al menos <b>hacerlo de la forma</b> que no hemos visto pero es la más desacoplada: <b>por XML</b>. En el wikibook viene un <a href="https://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Example_of_EntityListener_event_xml">ejemplo</a>.<br />
<br />Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-85033945492018031192020-03-31T21:18:00.000+02:002020-04-13T17:43:26.420+02:00ORM por XML: Guardar subclases en SINGLE_TABLEYa vimos en una <a href="https://www.hijosdelspectrum.com/2020/03/orm-por-xml-de-clases-con-herencia.html">sesión anterior</a> la capacidad de heredar la definición de persistencia desde las superclases con la etiqueta <code>"mapped-superclass"</code>. En esta entrada <b>vamos a ver cómo se guardan especializaciones distintas de un mismo tipo pudiendo recuperar todas ellas de forma única o recuperándolas como un tipo específico</b>.<br />
<br />
En nuestro ejemplo nuestras especializaciones son los distintos tipos de sucesos que hay en un partido (goles, tarjetas, corners, etc...) ya que todos heredan de <code>Suceso</code>, en nuestra API concretamente de la implementación <code>SucesoConId</code>.<br />
<br />
Para persistir varias clases que heredan de un mismo tipo <b>se puede hacer de tres formas</b>:<br />
<ol>
<li><b><a href="https://en.wikibooks.org/wiki/Java_Persistence/Inheritance#Single_Table_Inheritance"><code>SINGLE_TABLE</code></a></b>: Es la <b>opción por defecto y la implementada</b> por todas las librerías. Los datos de todos los subtipos se guardan en una misma tabla. <b>Tiene el mejor rendimiento</b> a la hora de consultar datos porque no hay que realizar ninguna unión, pero <b>como desventaja tendremos campos <code>null</code></b> en las columnas que no usen especificaciones concretas (tienen que existir columnas para todos los campos de todas las implementaciones)</li>
<li><b><a href="https://en.wikibooks.org/wiki/Java_Persistence/Inheritance#Joined,_Multiple_Table_Inheritance"><code>JOINED</code></a></b>: Todos los datos comunes se volcarán en una única tabla y el <b>resto de campos se guardará en otra tabla aparte con una FK</b> a la tabla común que lo relacione.</li>
<li><b><a href="https://en.wikibooks.org/wiki/Java_Persistence/Inheritance#Table_Per_Class_Inheritance"><code>TABLE_PER_CLASS</code></a></b>: Esta es una opción de JPA y puede que algunas implementaciones no la contempen. Se trata de guardar <b>cada clase entera en su propia tabla</b>.</li>
</ol>
De estas tres formas <b>nos vamos a centrar en <code>SINGLE_TABLE</code> por ser la más implementada y de mejor rendimiento aunque implique desnormalizar</b> nuestro modelo de datos en la BD.<br />
<br />
<h3>
¿Cómo se identifica qué tipo hay en cada fila?</h3>
Como vamos a mezclar tipos distintos en la misma tabla debe haber algún <b>mecanismo que permita saber qué tipo concreto hay en cada fila</b>. Para esto, además de todas las columnas para los datos de todas las distintas especializaciones se va a <b>crear una columna que sirva para discriminar el tipo</b>. Para implementar todo lo necesario lo voy a dividir en 4 pasos:<br />
<ol>
<li>Creación de las <b>dos especializaciones y sus DAO</b></li>
<li><b>ORM por XML</b> para <code>SucesoConId</code> y especialización de <code>Tarjeta</code></li>
<li><b>ORM por anotaciones</b> de especialización de <code>Gol</code></li>
<li><b>Modificar nuestro <code>ObjectMapper</code></b> como sea necesario</li>
</ol>
<a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/93e9f2b00eec1103cf1a319a0352322f8c4fd34d"><b>Paso 1</b></a>: Vamos a crearnos dos de estas especificaciones: <code>GolConId</code> y <code>TarjetaConId</code> que heredarán de <code>SucesoConId</code> y además implementarán sus respectivas interfaces (<code>Gol</code> y <code>Tarjeta</code>). También vamos a crear los DAO correspondientes como lo hicimos en la sesión anterior sobre Spring Data Rest. Éste código no tiene ningún misterio y se puede ver fácilmente en el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/93e9f2b00eec1103cf1a319a0352322f8c4fd34d">commit</a>.<br />
<br />
Como ya dijimos que <a href="https://en.wikibooks.org/wiki/Java_Persistence/Advanced_Topics#Interfaces">para JPA las interfaces no existen</a>, no caigamos en
la tentación de asignar las definiciones a las interfaces <code>Gol</code> y <code>Tarjeta</code> (ver fichero <code>Sucesos.orm.xml</code>): deben ser clases que se puedan instanciar. De hecho podríamos tener implementaciones distintas a <code>GolConId</code> y <code>TarjetaConId</code> (y que tuvieran que guardarse de forma distinta también) lo que implicaría otro valor para discriminarlas. Como hay dos especificaciones voy a hacer ORM de formas distintas con cada una:<br />
<br />
<a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/4ce88421c65186181c6a7505d34acf881bd130fb"><b>Paso 2</b></a>: <code>Tarjeta</code> por XML (lo añado a <code>SucesoConId.orm.xml</code> para que se vea que puede haber varias entidades en el mismo fichero):<br />
<pre><code><entity class="es.lanyu.eventos.repositorios.SucesoConId" access="FIELD">
<table name="SUCESOS"/>
<b><!-- No hace falta strategy es el valor por defecto -->
<inheritance strategy="SINGLE_TABLE"/>
<discriminator-column name="TIPO"/>
<discriminator-value>S</discriminator-value></b>
<attributes>
...
</attributes>
</entity>
<b><entity class="es.lanyu.eventos.repositorios.TarjetaConId" access="FIELD">
<discriminator-value>T</discriminator-value>
<attributes>
<basic name="tipoTarjeta"/>
</attributes>
</entity></b></code></pre>
<a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/fc59fc76d58b5a8465fd3f058394983771ddb0ac"><b>Paso 3</b></a>: Y <code>Gol</code> lo haré por anotaciones añadiéndole lo que hace falta a la clase <code>GolConId</code>:<br />
<pre><code>@Entity
@Access(value=AccessType.FIELD)
@DiscriminatorValue("G")
public class GolConId extends SucesoConId implements Gol { ... }</code></pre>
<div class="nota">
NOTA: ver <a href="https://en.wikibooks.org/wiki/Java_Persistence/Mapping#Access_Type">@Access</a>.
</div>
<br />
<a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/0348727796dedbefeb8e26099ff1adc7b45a8bc0"><b>Paso 4</b></a>: Ahora toca tunear nuestro <code>MixIns</code> para añadir y reutilizar código (implementación y extensión de <code>ContadorDeMinutos</code> y refactorizado de <code>Datables-Partidos-Sucesos</code>). Como no es parte de esta sesión mejor verlo en el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/0348727796dedbefeb8e26099ff1adc7b45a8bc0">commit</a> o el video del webinar.<br />
<br />
Con esto <b>ya podemos levantar el servicio y empezar a añadir también goles y tarjetas. Veremos que crea URLs para los recursos <code>/goles</code> y <code>/tarjetas</code></b> donde podemos ver cada uno de ellos <b>por separado o todos juntos en el recurso de <code>/sucesos</code></b> pero con relaciones agrupándo cada tipo distinto.<br />
<br />
Se puede obtener el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/0348727796dedbefeb8e26099ff1adc7b45a8bc0">código hasta aquí</a> en su repositorio. Se han añadido peticiones a la colección de Postman para generar goles y tarjetas aleatorias.<br />
<br />
<div class="postman-run-button" data-postman-action="collection/import" data-postman-var-1="a390ad12b3ffc8e3ef4f">
</div>
<script type="text/javascript">
(function (p,o,s,t,m,a,n) {
!p[s] && (p[s] = function () { (p[t] || (p[t] = [])).push(arguments); });
!o.getElementById(s+t) && o.getElementsByTagName("head")[0].appendChild((
(n = o.createElement("script")),
(n.id = s+t), (n.async = 1), (n.src = m), n
));
}(window, document, "_pm", "PostmanRunObject", "https://run.pstmn.io/button.js"));
</script>Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0tag:blogger.com,1999:blog-4756110956436481426.post-19093092890629949692020-03-29T23:47:00.000+02:002020-04-06T11:56:30.698+02:00La potencia de Spring Data Rest: @RestResourceYa hemos estado viendo lo básico del <a href="https://www.hijosdelspectrum.com/2020/03/spring-framework-lo-basico.html">Core</a> de Spring, también hemos estado viendo lo básico de <a href="https://www.hijosdelspectrum.com/2020/03/entidades-y-repositorios-con-jpa.html">JPA/ORM</a> para dar persistencia a los datos. Ahora <b>vamos a ver la capa de presentación mostrando nuestros datos por una API REST</b>.<br />
<br />
<b><a href="https://es.wikipedia.org/wiki/Transferencia_de_Estado_Representacional">REST</a> se clasifica en varios </b><a href="https://blog.restcase.com/4-maturity-levels-of-rest-api-design/"><b>niveles</b> en función de su madurez</a>. En general basta con un <b>servicio web que atienda peticiones por protocolo HTTP y devuelva datos serializados</b> (normalmente en JSON, pero puede ser otro formato también): <a href="https://www.martinfowler.com/articles/richardsonMaturityModel.html#level0">Nivel 0</a>. Estos dos puntos parecen antagónicos con <a href="https://es.wikipedia.org/wiki/Simple_Object_Access_Protocol">SOAP</a> que permite cualquier protocolo para el transporte, pero los datos deben intercambiarse por XML con un esquema rígido.<br />
<br />
<b>URLs bien definidas identificando recursos</b>: colecciones, elementos individuales y otras "operaciones": <a href="https://www.martinfowler.com/articles/richardsonMaturityModel.html#level1">Nivel 1</a>.<br />
<br />
Para las peticiones se <b>usarán verbos HTTP</b> para operaciones CRUD (<code>GET</code>, <code>POST</code>, <code>PUT</code>, <code>DELETE</code>,...): <a href="https://www.martinfowler.com/articles/richardsonMaturityModel.html#level2">Nivel 2</a>.<br />
<br />
<b>El nivel más alto se logra consiguiendo </b><a href="https://en.wikipedia.org/wiki/HATEOAS"><b>HATEOAS</b>/Hipermedia</a>. Esto significa que, desde la raíz de nuestro servicio REST, todas las llamadas que se puedan realizar deben ser autodescubribles por medio de enlaces URL que representan las asociaciones de los distintos recursos: <a href="https://www.martinfowler.com/articles/richardsonMaturityModel.html#level3">Nivel 3</a>. Un ejemplo sería la <a href="https://swapi.co/">API con datos sobre Star Wars</a> donde se ven los enlaces de Luke con su planeta natal, peliculas donde sale, sus vehículos, raza, etc... siendo todos ellos urls al recurso (pero no incrustando sus datos).<br />
<br />
Otra característica es que <b>REST no mantiene estado</b>. Cada petición y respuesta debe contener toda la información necesaria.<br />
<br />
Después de esta introducción, recomiendo ver un video que habla de todo ello de una forma muy práctica y directa llamado <a href="https://www.youtube.com/watch?v=Kex0ty5eHIw">"Horneando APIs" de Paradigma Digital</a>. y que para mí es como un resumen en español del libro <a href="https://www.oreilly.com/library/view/rest-api-design/9781449317904/">REST API Design Rulebook</a>. Nosotros vamos a poner en práctica todo esto con:<br />
<ol>
<li><a href="https://docs.spring.io/spring-data/rest/docs/current/reference/html/#reference">Spring Data Rest</a>: que es una de las dependencias que usamos cuando generamos el proyecto
inicialmente con Spring Initialzr. Si miramos nuestro <code>build.gradle</code> se
identifica perfectamente la dependencia</li>
<li>y nuestro ejemplo de Datos Deportivos</li>
</ol>
Empezamos este tutorial dando persistencia a <code>Participante</code>s y a <code>Partido</code>s que tenía una colección de <code>Suceso</code>s. Cargamos los datos de 237 participantes usando su DAO correpondiente y también cargamos unos partidos aleatorios con sucesos (en general sin decir si son goles, tarjetas, etc...). Esta forma de trabajar no permite la interacción que se espera de una arquitectura cliente-servidor. <b>Al final de esta entrada tendremos una colección para <a href="https://www.postman.com/">Postman</a> que nos permitirá interaccionar con nuestra aplicación</b> mediante llamadas HTTP y nos permitirá hacer pruebas más fácil. Por tanto el punto inicial es este <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/9dfaefe672a55666dab193d6d6f6a6ee8a2d4b0e">commit de nuestro repositorio</a>.<br />
<br />
Si en este punto no cerramos la aplicación con nuestra última linea del <code>main</code> veríamos que ya se están exponiendo los repositorios. Hacemos una llamada a <code>http://localhost:8080/</code> y tendremos la siguiente respuesta:<br />
<pre><code>{
"_links": {
"partidoConIds": {
"href": "http://localhost:8080/partidoConIds{?page,size,sort}",
"templated": true
},
"participantes": {
"href": "http://localhost:8080/participantes{?page,size,sort}",
"templated": true
},
"sucesoConIds": {
"href": "http://localhost:8080/sucesoConIds{?page,size,sort}",
"templated": true
},
"profile": {
"href": "http://localhost:8080/profile"
}
}
}</code></pre>
Es decir, <a href="https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources">por defecto, todos los repositorios serán mostrados por Spring Data Rest</a> si no se configura lo contrario. Voy añadir dos <a href="https://docs.spring.io/spring-data/rest/docs/current/reference/html/#getting-started.changing-other-properties">propiedades</a> para configurar dos aspectos: que sólo se muestre lo anotado con <code>@(Repository)<a href="https://docs.spring.io/spring-data/rest/docs/current/api/org/springframework/data/rest/core/annotation/RestResource.html">RestResource</a></code> y el <a href="https://docs.spring.io/spring-data/rest/docs/current/reference/html/#getting-started.changing-base-uri">prefijo de la API</a>:<br />
<pre><code>spring.data.rest.detection-strategy=annotated
spring.data.rest.basePath=/api</code></pre>
Esto lo pondré en un archivo llamada <code>rest.properties</code> que <a href="https://www.hijosdelspectrum.com/2020/03/propiedades-y-value.html">añadiré con <code>@PropertySource</code> como vimos anteriormente</a> mediante una clase de configuración llamada <code>ConfiguracionJava</code> que luego usaré para más cosas:<br />
<pre><code>@Configuration
@PropertySource({ "classpath:config/rest.properties" })
public class ConfiguracionPorJava {}</code></pre>
Y la añadimos a nuestra aplicación:<br />
<pre><code class="java">@Import(ConfiguracionPorJava.class)
public class DatosdeportivosapiApplication {...}</code></pre>
Ahora ya no se mostrará lo anterior, así que lo siguiente será modificar nuestras anotaciones <code>@Repository</code> por <code>@RepositoryRestResource</code> que es de Spring Data Rest.<br />
<br />
<a href="https://docs.spring.io/spring-data/rest/docs/2.0.0.RELEASE/api/org/springframework/data/rest/core/annotation/RepositoryRestResource.html">@RepositoryRestResource</a> admite varios valores para configurar nuestro repositorio. Pongo el ejemplo para <code>SucesoConId</code>:<br />
<pre><code>@RepositoryRestResource(path="sucesos",
// exported=false,
itemResourceRel="suceso",
collectionResourceRel="sucesos")
public interface SucesoDAO extends JpaRepository<SucesoConId, Long> {
}</code></pre>
Aquí se puede ver que podemos decidir el path para este recurso y los nombres que tendrán las relaciones tanto para colección como para elemento (al final de la entrada se ve qué es esto de las relaciones). <b>Recomiendo usar al menos esta configuración</b> porque de lo contrario generará esos nombres automáticamente y puede que el resultado no nos guste (ver cómo se veía antes más arriba).<br />
<br />
He comentado el valor <code>exported=false</code> ya que evita que se exponga el recurso. Este valor habría que usarlo si no quisiéramos mostrar este recurso. Por defecto tiene valor <code>true</code>.<br />
<br />
Con esto <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/05f5f16b897d7f828a1810a92816d7ec7760f3b6">termina el Paso 1</a>, pero los recursos de <code>Partido</code>s y <code>Sucesos</code> no se pueden visitar ya que Jackson no es capaz de serializarlos correctamente porque no son simples POJOs. Vamos a <b>customizar cómo debe serializarse</b> aplicando propiedades para jackson, implementando unos MixIn y añadiéndolos a nuestro <code>ObjectMapper</code>. Como la entrada no va de esto y para no alargarla, lo mejor es ver este segundo paso en vivo en el webinar, pero aquí <b>nos vamos a colocar en el <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/541a240b33c1ebde820a2106eb292b8fc0fa3d7a">commit del Paso 2</a> para continuar y terminar con nuestra API RESTful HATEOAS</b>.<br />
<br />
El resultado hasta aquí muestra una API con un array de <code>Sucesos</code> en cada <code>Partido</code> y sin mostrar el <code>partido</code> al que corresponde cada <code>Suceso</code> para no entrar en bucle. También vemos links de paginación dando cierta navegabilidad, pero no se puede navegar entre <code>Partido</code>s y <code>Suceso</code>s correctamente. <b>Para implementar HATEOAS correctamente Spring necesita</b> la siguiente información sobre las relaciones:<br />
<ol>
<li><b>Anotar con <code>@OneToMany</code> y <code>@ManyToOne</code></b> los miembros necesarios. No sirve sólo con que tenga la información JPA.</li>
<li>Los recursos para asociar deben <a href="https://docs.spring.io/spring-data/rest/docs/current/reference/html/#repository-resources">tener <b>repositorio propio</b></a> (esto ya está hecho)</li>
<li>Se debe <b>conocer la <a href="https://docs.spring.io/spring-data/rest/docs/current/reference/html/#representations">implementación concreta</a></b> si la relación hace referencia <b>a un tipo abstracto</b> (debe saber qué <code>Suceso</code> debe deserializarse: <a href="https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization">deserialización polimórfica</a>)</li>
</ol>
Para el primer punto no hace falta anotar las clases con <code>@Entity</code> ni <code>@Id</code>. Con <b>anotar el miembro correspondiente</b> es suficiente. Como no se tiene acceso al campo sucesos lo que hacemos es sobre escribirlo y anotarlo <b>incluyendo la entidad objetivo</b> (punto 3):<br />
<pre><code>@Override
@OneToMany(targetEntity=SucesoConId.class)
public Collection<Suceso> getSucesos() {
return super.getSucesos();
}</code></pre>
<div class="nota">
NOTA: también podría hacerse con <a href="https://stackoverflow.com/a/10349257">(de)serializadores personalizados</a>, añadiendo información con <a href="https://github.com/FasterXML/java-classmate#examples"><code>TypeResolver</code></a> (y <a href="https://fasterxml.github.io/jackson-databind/javadoc/2.8/com/fasterxml/jackson/databind/jsontype/TypeResolverBuilder.html"><code>TypeResolverBuilder</code></a>, sobre <a href="https://github.com/FasterXML/jackson-databind/wiki/Databind-Annotations#polymorphic-type-handling"><code>@JsonType(Id)Resolver</code></a> o ver este <a href="https://stackoverflow.com/a/17932003">snippet</a>). Es la solución más genérica que se encuentra (aunque rebuscando mucho, una búsqueda me dio sólo 3 resultados) en Internet, pero en <b>nuestro caso particular usando semántica esta forma es muy simple y funciona</b>.
</div>
<br />
Para el caso de <code>SucesoConId</code> basta con anotar el campo con <code>@ManyToOne</code>.<br />
<pre><code>@ManyToOne
PartidoConId partido;</code></pre>
Comparto también una <a href="https://www.getpostman.com/collections/a390ad12b3ffc8e3ef4f">colección</a>, para probar la <a href="https://github.com/LanyuEStudio/Datos-Deportivos-API/commit/96a12b4d7efe32b8c3b68e826a30c825bf9fd5e7">API en el punto actual</a>.Awes0meM4nhttp://www.blogger.com/profile/02484265418385179921noreply@blogger.com0