Duda JAVA - API REST + springboot

0nLy

Hola, voy al grano.

Básicamente me han pedido para una prueba hacer un web service CRUD con REST de una clase Producto (id, name y description).

Lo típico, me he hecho el controller, service y repository, utilizando JPA e hibernate para persistir los datos a una bbdd postgres. Todo ok.

El tema es que en la prueba me dicen esto y no tengo ni puta idea de qué es lo que tengo que hacer:
- Products should be kept in memory in the java collection you think best fit.

Entiendo que puede ser algo relaccionado con Collections o caché o algo por el estilo en el respository, pero nunca he trabajado con nada así y necesito alguien que me ilumine. El problema es que no sé ni como buscar ayuda en google porque no sé qué buscar...

Alguien sabe de qué me están hablando? gracias :)

-Yepeto-

Yo lo que entiendo es que te está diciendo que uses el que mejor creas que se adapte, una lista o las distintas implementaciones, un mapa o lo que tu creas que es mejor para almacenar esos datos en memoria.

1 respuesta
0nLy

#2 Ya, ya, si el problema es que no he hecho nunca nada relacionado con almacenar datos en memoria. Entonces no sé ni cómo hacerlo, ni cómo buscar documetnación para hacerlo.

Yo lo único que he hecho ha sido persistir datos en base de datos y recuperarlos de ahí, nunca he trabajado con memoria.

Tienes algun link con documentación de como se podría hacer?

Fyn4r

Hay algo que no cuadra, es decir, la memoria es volátil, asi que hablar de persistencia chungo. Entonces o eso se refiere a que no se requiere de persistencia o a que cuando cargues los datos los metas en la estructura de datos (o subclase de collections o como vaya eso en java xd) que mejor se adapte a las necesidades (por ejemplo si vas a buscar por id pues lo metes en un mapa porque es O(1), esas cosas, supongo)

1 1 respuesta
0nLy

#4 Si, creo que va más enfocado a lo que dices, en la explicación que me han dado sobre cómo hacer esa parte he apuntado esto:

  • Usar Collections
  • Hacerse un productcollectionrepository
  • implementacion de esa interfaz - que tenga un map (id,producto) y dentro se implementan los métodos
  • Streams filtrado

Pero aún con esto sigo un poco perdido xD

desu

Pregunta tonta, seguro que te pedian persistencia en db? Si es asi.

Yo creo que lo que quieren que hagas es, Repository por entidad y ademas tener un ProductRepository, con su InMemoryProductRepository que lo implemente, y dentro tener un hashmap.

Depende de los casos de uso que te proponen pues podrias implementar una LRU o mirar de optimizar algunas queries.

Protip, yo usaria un approximated LRU ;)

2 respuestas
-Yepeto-

A ver, dices que ya tienes el repositorio no? Pues los métodos que tengas definidos tendrán un tipo como return.

Si es un getById pues el return será el objeto Product. Si es un getAll por norma general, es un List<Product> o en este casi sí lo quieres más eficiente guardarlo en un Map<Integer, Product> pero no se si el repositorio es capaz de devolverlo así directamente (entiendo que si con alguna configuración concreta).

Eso es lo que entiendo yo en esa frase, que elijas como quieres guardar los datos que consultes de base de datos, si no, la persistencia es en memoria.

¿Que base de datos estás usando?

1 respuesta
0nLy

#6 #7 Si, creo que el error ha estado en persistir en la base de datos, no me lo pedían en ningún momento.
El repository que he hecho ha sido extendiendolo de JPA para que pille el CRUD y luego añadir yo 2 búsquedas más que me pedían:

public interface ProductRepository extends JpaRepository<Product, Long>{
	
 List<Product> findByName(String name);
 
 List<Product> findByDescription(String description);
}
0nLy

De hecho, os copio literal el texto:


PRODUCT SERVICE
We need to create a simple WEB service exposing an CRUD rest api to manage products.
Additionally the api will allow searching by product attributes.
Product attributes

  • ID
  • Name
  • Description

Notes:

  • Creating operation should have two entry points, sync(rest api) and async(event listener, it
    can be just a regular method for the sake of simplicity).
  • Products should be kept in memory in the java collection you think best fit.[/i]

edit: #6 podría ser lo que dices algo así?: https://github.com/flojdek/spring-boot-java-lru-cache-rest

desu

por el enunciado completo no me hagas caso y céntrate en mejorar las búsquedas / indexados.

si quieres seguir usando hibernate i.e

https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#getting-started

Así encontraras productos basándote en keyboards de la descripción. Si usas psql no necesitas usar ningun motor externo, psql ya tiene uno, no se si con hibernate lo puedes activar. Sino crea el gin sobre descripcion a mano.

Otra opción facil y manual para encontrar productos por sus atributos, si no quieres implementar a mano algo mas complejo, es sacar las palabras clave de la descripcion y ordenar por frecuencia. indexar apuntando palabra -> productId

Si el usuario busca "coche" y "descapotable" agregar los productId que contengan estas palabras y devolverlos por su importancia (basado en frecuencia)

A aqui vigilar cuando se haga delete o update de la descripcion porque toca recalcular los indices

No se, yo haria estas cosas

bornex

#1 creo que el requisito esta bastante claro hacia que va dirigido, debes usar una de las colecciones concurrentes de Java (e.g. ConcurrentHashMap, CopyOnWriteArrayList, etc...) vaya te la han puesto para pillarte y si no llegas a postear por aquí te la hubieras comido con papas.

Saber Java es ideal antes de aprender Spring...

EDIT: También puedes usar una de las bases de datos in-memory que Spring Data soporta (p.e. h2). ¿Sabrías responder a la pregunta de porque se necesita una colección concurrente para mantener los datos en memoria?

2 respuestas
0nLy

#11 Es la primera vez que leo sobre concurrencia, por lo que veo en vez de bloquear todo el mapa bloquea solo la parte que va utilizar, eso está guay, pero no entiendo por qué se necesita para mantener los datos en memoria.

pd: Creo que voy a tirar por el h2 que tiene buena pinta, muchas gracias! :)

1 respuesta
Traber

#12 Porque siendo una API Rest tiene que ser concurrente en el acceso a la colección así como a la escritura, porque si no el rendimiento se hará caca encima.

3 respuestas
0nLy

#13 Ah, joder, claro, por eso recalcan esto:

  • Products should be kept in memory in the java collection you think best fit.
  • Focus on the quality of the code and performance of the solution, think it will be called by
    millions of clients
    [/i]

Tiene que ser concurrente ya que al hacerle muchas peticiones a la vez el rendimiento va a seguir siendo aceptable, de la otra forma se forman "colas" en las peticiones, no?

desu

Vas muy perdido, postea todas tus dudas / respuestas que dirías al entrevistador y te ayudamos xd

Lecherito

#13 el porque se usa un concurrenthashmap no es por el rendimiento eh xD

1
bornex

#13 no tiene absolutamente nada que ver el rendimiento con que la colección sea thread safe.

1 respuesta
Kaledros

Es mucho más sencillo. Este ejemplo lo vi en un curso de Spring Boot.

Básicamente creas una clase que extienda de CrudRepository. Creas una variable global (el ejemplo usaba un HashMap<String, Object>) y sobreescribes los métodos que necesites (save(), findAll(), etc) para que escriban y lean del hashmap. Y a tirar millas.

1 respuesta
bornex

#18 xD Pedazo de curso

1 respuesta
0nLy

Joder, me ha costado lo suyo pero al final creo que lo tengo.

He borrado todo el tema de postgresql, y lo he hecho con h2, ha funcionado bien, pero no veía como meter el tema de la concurrencia ahí, después de unas vueltas a mi cabeza (recalco que es la primera vez que veo este tema de bbdd en memoria y concurrencia en mi vida), he borrado h2 y la interfaz donde tenía el repositorio, lo he cambiado a un @service y he hecho toda la lógica con el ConcurrentHashMap y parece que funciona bien!

Algo así ha quedado (resumido):

spoiler
1 respuesta
Kaledros

#19 Era el primer ejemplo, coño XDD Luego ya usaba H2 y después Mongo.

Edit: este era el ejemplo.

public abstract class AbstractMapService<T, ID> {

protected Map<ID, T> map = new HashMap<>();

Set<T> findAll(){ return new HashSet<>(map.values());}

T findById(ID id){ return map.get(id); }

T save(ID id, T object){
    map.put(id, object);
    return object;
}

void deleteById(ID id){ map.remove(id); }

void delete(T object){ map.entrySet().removeIf(entry -> entry.getValue().equals(object)); }
}
bornex

#20 muy bien, ahora aprende el porqué eso funciona y porque has usado esa colección y no otra. ¿Que es lo que pasa si dos clientes a la vez intentarán escribir en un HashMap?

1 respuesta
0nLy

#22 Que el primero bloquearía el map hasta que termine el proceso, después pasaría al segundo (creo).

Con el ConcurrentHashMap el primero solo bloquea su objeto y el segundo puede entrar sin esperar.

¿Algo así, no?

2 respuestas
tarzanete

#23 para que el primero bloquee el map "algo" debe bloquearlo, o lo haces tú o usas concurrenthashmap que se encarga java de sincronizar. Es eso o te comes condición de carrerae inconsistencia de datos en ese mapa.

MTX_Anubis

#23 con un hashmap normal no bloquea nada. El principal problema es cuando añades/eliminas elementos del hashmap y tiene que hacer un rehash.

Las lecturas son lockfree en ambos, las escrituras están sincronizadas en el concurrent.

Pero vamos aún así. Si haces cosas como

val ent = repo.findById(1)
...
ent.updateStock(X)
repo.save(ent)

Con peticiones concurrentes pues va a estar mal también. La forma de arreglarlo sin los distintos tipos de transacciones ni select for share/update es con versionado (vamos un optimistic locking) o hacer en algo parecido a actores de tal forma que todas los updates de un producto con id X fueran al mismo actor (que se escapa por mucho de lo que te piden) o con disruptor filtrando eventos. Sin contar con los syncronized/locks de toda la vida vamos.

JuAn4k4

Siendo un ejercicio para clase, creo que lo que te dice es que en vez de usar una BD como poatgreslo pongas en memoria. No se si te valdrá meter hsqldb y ya está.

1 2 respuestas
Kaledros
#26JuAn4k4:

poatgreslo

¿Estás bien, tío?

2 respuestas
Tranc0s

Ya que te están guiando para que uses Collections deberías también ver porqué tienes que hacer override de equals() y hashCode(). Deberías leer sobre el contrato del equals y sobre el contrato de hashcode ( te recomiendo que lo mires en el libro Effective Java).

Pregúntate también que pasaria si estás usando un HashMap y no has hecho override del hashCode().

1 respuesta
Ranthas

#27 Es como escribir Chuacheneguer, nadie atina nunca, jamás

#28 Prácticamente siempre que trabajes con colecciones vas a tener que sobreescribir equals y hashcode; en mi opinión es una práctica que debería seguirse siempre, aunque no sea necesaria. No hacerlo puede tener efectos la mar de divertidos, sobre todo cuando trabajas con colecciones que aseguran la unicidad de sus componentes, como Set.

B

.

1 respuesta