JavaRush /Blog Java /Random-ES /Una guía para los microservicios de Java. Parte 3: pregun...

Una guía para los microservicios de Java. Parte 3: preguntas generales

Publicado en el grupo Random-ES
Traducción y adaptación de microservicios Java: una guía práctica . Partes anteriores de la guía: Veamos los problemas inherentes de los microservicios en Java, comenzando con cosas abstractas y terminando con bibliotecas concretas. Una guía para los microservicios de Java.  Parte 3: preguntas generales - 1

¿Cómo hacer que un microservicio Java sea resistente?

Recuerde que cuando crea microservicios, básicamente está intercambiando llamadas a métodos JVM por llamadas HTTP sincrónicas o mensajería asincrónica. Si bien se garantiza que una llamada a un método se completará (salvo un cierre inesperado de la JVM), una llamada a la red no es confiable de forma predeterminada. Puede que funcione, pero puede que no funcione por varios motivos: la red está sobrecargada, se ha implementado una nueva regla de firewall, etc. Para ver cómo esto marca la diferencia, echemos un vistazo al ejemplo de BillingService.

Patrones de resiliencia HTTP/REST

Digamos que los clientes pueden comprar libros electrónicos en el sitio web de su empresa. Para hacer esto, acaba de implementar un microservicio de facturación que puede llamar a su tienda en línea para generar facturas en PDF reales. Por ahora, haremos esta llamada de forma sincrónica, a través de HTTP (aunque tiene más sentido llamar a este servicio de forma asincrónica, ya que la generación de PDF no tiene que ser instantánea desde la perspectiva del usuario. Usaremos el mismo ejemplo en el siguiente sección y observe las diferencias).
@Service
class BillingService {

    @Autowired
    private HttpClient client;

     public void bill(User user, Plan plan) {
        Invoice invoice = createInvoice(user, plan);
        httpClient.send(invoiceRequest(user.getEmail(), invoice), responseHandler());
        // ...
    }
}
Para resumir, aquí hay tres posibles resultados de esta llamada HTTP.
  • OK: la llamada se realizó, la cuenta se creó exitosamente.
  • DEMORA: La llamada se realizó, pero tardó demasiado en completarse.
  • ERROR. La llamada falló, es posible que haya enviado una solicitud incompatible o que el sistema no esté funcionando.
Se espera que cualquier programa maneje situaciones de error, no sólo las exitosas. Lo mismo se aplica a los microservicios. Incluso si necesita hacer un esfuerzo adicional para garantizar que todas las versiones implementadas de la API sean compatibles una vez que comience con las implementaciones y lanzamientos de microservicios individuales. Una guía para los microservicios de Java.  Parte 3: preguntas generales - 2Un caso interesante a analizar es el del retraso. Por ejemplo, el disco duro del microservicio del respondedor está lleno y, en lugar de tardar 50 ms, tarda 10 segundos en responder. Se vuelve aún más interesante cuando experimenta una cierta carga tal que la falta de respuesta de su BillingService comienza a afectar su sistema. Como ejemplo ilustrativo, imagine una cocina que poco a poco va poniendo en marcha un “bloque” de todos los camareros de un restaurante. Obviamente, esta sección no puede proporcionar una descripción general exhaustiva del tema de la resiliencia de los microservicios, pero sirve como recordatorio para los desarrolladores de que esto es algo que en realidad debe abordarse y no ignorarse antes de su primer lanzamiento (lo cual, por experiencia, sucede con más frecuencia que no). sigue). Una biblioteca popular que le ayuda a pensar en la latencia y la tolerancia a fallos es Hystrix de Netflix. Utilice su documentación para profundizar en el tema.

Patrones de resiliencia de mensajes

Echemos un vistazo más de cerca a la comunicación asincrónica. Nuestro programa BillingService ahora podría verse así, suponiendo que estemos usando Spring y RabbitMQ para enviar mensajes. Para crear una cuenta, ahora enviamos un mensaje a nuestro broker de mensajes RabbitMQ, donde hay varios trabajadores esperando nuevos mensajes. Estos trabajadores crean facturas en PDF y las envían a los usuarios adecuados.
@Service
class BillingService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

     public void bill(User user, Plan plan) {
        Invoice invoice = createInvoice(user, plan);
        // преобразует счет, например, в json и использует его Cómo тело mensajes
        rabbitTemplate.convertAndSend(exchange, routingkey, invoice);
        // ...
    }
}
Los errores potenciales se ven un poco diferentes ahora, ya que ya no recibe respuestas inmediatas de OK o ERROR como lo hacía con una conexión HTTP sincrónica. En cambio, es posible que tengamos tres escenarios potenciales que podrían salir mal, lo que podría plantear las siguientes preguntas:
  1. ¿Mi mensaje fue entregado y utilizado por el empleado? ¿O está perdido? (El usuario no recibe factura).
  2. ¿Mi mensaje fue entregado solo una vez? ¿O se entregó más de una vez y se procesó sólo una vez? (El usuario recibirá múltiples facturas).
  3. Configuración: Desde "¿Utilicé las claves/nombres de enrutamiento correctos para el intercambio" hasta "¿Mi agente de mensajes está configurado y mantenido correctamente o sus colas están llenas?" (El usuario no recibe factura).
Una descripción detallada de cada patrón de resiliencia de microservicio asincrónico individual está fuera del alcance de esta guía. Sin embargo, hay señales en la dirección correcta. Además, dependerán de la tecnología de mensajería. Ejemplos:
  • Si está utilizando implementaciones JMS como ActiveMQ, puede cambiar la velocidad por la garantía de confirmaciones de dos fases (XA).
  • Si está utilizando RabbitMQ, lea primero esta guía y luego piense detenidamente sobre las confirmaciones, la tolerancia a fallas y la confiabilidad de los mensajes en general.
  • Quizás alguien esté bien versado en la configuración de servidores Active o RabbitMQ, especialmente en combinación con clustering y Docker (¿alguien? ;))

¿Qué marco sería la mejor solución para los microservicios de Java?

Por un lado, podrás instalar una opción muy popular como es Spring Boot . Hace que sea muy fácil crear archivos .jar, viene con un servidor web integrado como Tomcat o Jetty y se puede ejecutar rápidamente y en cualquier lugar. Ideal para crear aplicaciones de microservicios. Recientemente, aparecieron un par de marcos de microservicios especializados, Kubernetes o GraalVM , parcialmente inspirados en la programación reactiva. Aquí hay algunos contendientes más interesantes: Quarkus , Micronaut , Vert.x , Helidon . Al final, tendrá que elegir usted mismo, pero podemos darle un par de recomendaciones que pueden no ser completamente estándar: con la excepción de Spring Boot, todos los marcos de microservicios generalmente se comercializan como increíblemente rápidos, con un inicio casi instantáneo. , bajo uso de memoria, escalabilidad hasta el infinito. Los materiales de marketing suelen presentar gráficos impresionantes que muestran la plataforma junto al gigante Spring Boot o entre sí. Esto, en teoría, ahorra los nervios a los desarrolladores que apoyan proyectos heredados, que a veces tardan varios minutos en cargarse. O desarrolladores que trabajan en la nube y desean iniciar/detener tantos microcontenedores como necesitan actualmente en 50 ms. Una guía para los microservicios de Java.  Parte 3: preguntas generales - 3El problema, sin embargo, es que estos tiempos (artificiales) de puesta en marcha y de reimplementación apenas contribuyen al éxito general del proyecto. Al menos influyen mucho menos que una sólida infraestructura marco, una sólida documentación, una comunidad y sólidas habilidades de desarrollador. Entonces es mejor verlo de esta manera: Si hasta ahora:
  • Deja que sus ORM funcionen desenfrenadamente, generando cientos de consultas para flujos de trabajo simples.
  • Necesitas un sinfín de gigabytes para ejecutar tu monolito moderadamente complejo.
  • Tiene tanto código y la complejidad es tan alta (ahora no estamos hablando de arrancadores potencialmente lentos como Hibernate) que su aplicación tarda varios minutos en cargarse.
Si este es el caso, agregar problemas de micoservicio adicionales (resiliencia de la red, mensajería, DevOps, infraestructura) tendrá un impacto mucho mayor en su proyecto que cargar un Hola, mundo vacío. Y para las redespliegues en caliente durante el desarrollo, es posible que termine con soluciones como JRebel o DCEVM . Tomémonos un momento para citar nuevamente a Simon Brown : " Si las personas no pueden construir monolitos (rápidos y eficientes), les resultará difícil construir microservicios (rápidos y eficientes), independientemente de su estructura ". Así que elija sabiamente sus marcos.

¿Qué bibliotecas son mejores para llamadas REST de Java sincrónicas?

En el aspecto técnico de bajo nivel, probablemente terminará con una de las siguientes bibliotecas cliente HTTP: HttpClient nativo de Java (desde Java 11), HttpClient de Apache u OkHttp . Tenga en cuenta que digo "probablemente" aquí porque hay otras opciones, que van desde los antiguos clientes JAX-RS hasta los modernos clientes WebSocket . En cualquier caso, la tendencia es generar un cliente HTTP, dejando de jugar con las llamadas HTTP usted mismo. Para hacer esto, debe echar un vistazo al proyecto OpenFeign y su documentación como punto de partida para seguir leyendo.

¿Cuáles son los mejores intermediarios para la mensajería Java asincrónica?

Lo más probable es que te encuentres con los populares ActiveMQ (Classic o Artemis) , RabbitMQ o Kafka .
  • ActiveMQ y RabbitMQ son intermediarios de mensajes tradicionales y completos. Implican la interacción de un "corredor inteligente" y "usuarios estúpidos".
  • Históricamente, ActiveMQ ha tenido la ventaja de una fácil integración (para pruebas), que se puede mitigar con la configuración de RabbitMQ/Docker/TestContainer.
  • No se puede decir que Kafka sea un intermediario “inteligente” tradicional. En cambio, es un almacén de mensajes (archivo de registro) relativamente "tonto" que requiere que los consumidores inteligentes lo procesen.
Para comprender mejor cuándo usar RabbitMQ (u otros intermediarios de mensajes tradicionales en general) o Kafka, consulte esta publicación de Pivotal como punto de partida. En general, al elegir un corredor de mensajería, intente ignorar las razones artificiales de rendimiento. Hubo un momento en que los equipos y las comunidades en línea discutían constantemente sobre qué tan rápido era RabbitMQ y qué tan lento era ActiveMQ. Ahora se hacen los mismos argumentos con respecto a RabbitMQ, dicen que funciona lentamente con entre 20 y 30 mil mensajes por segundo. Kafka registra 100 mil mensajes por segundo. Hablando francamente, tales comparaciones son como comparar lo cálido con lo suave. Además, en ambos casos los valores de rendimiento pueden estar en el rango bajo a medio de, por ejemplo, Alibaba Group. Sin embargo, es poco probable que en la realidad te hayas encontrado con proyectos de esta escala (millones de mensajes por minuto). Definitivamente existen y tendrían problemas. A diferencia del otro 99% de los proyectos empresariales Java "normales". Así que no prestes atención a la moda y las exageraciones. Elegir sabiamente.

¿Qué bibliotecas puedo usar para probar microservicios?

Depende de tu pila. Si tiene implementado un ecosistema Spring, sería prudente utilizar las herramientas específicas del marco . Si JavaEE es algo así como Arquillian . Podría valer la pena echarle un vistazo a Docker y a la realmente buena biblioteca Testcontainers , que ayuda en particular a configurar fácil y rápidamente una base de datos Oracle para el desarrollo local o pruebas de integración. Para realizar pruebas simuladas de servidores HTTP completos, consulte Wiremock . Para probar la mensajería asincrónica, intente implementar ActiveMQ o RabbitMQ y luego escriba pruebas usando Awaitility DSL . Además, se utilizan todas las herramientas habituales: Junit , TestNG para AssertJ y Mockito . Tenga en cuenta que esta no es una lista completa. Si no encuentra su herramienta favorita aquí, publíquela en la sección de comentarios.

¿Cómo habilitar el registro para todos los microservicios de Java?

El registro en el caso de los microservicios es un tema interesante y bastante complejo. En lugar de tener un archivo de registro que pueda manipular con comandos less o grep, ahora tiene n archivos de registro y desea que no estén demasiado dispersos. Las características del ecosistema maderero están bien descritas en este artículo (en inglés). Asegúrese de leerlo y prestar atención a la sección Registro centralizado desde una perspectiva de microservicios . En la práctica, encontrará diferentes enfoques: el administrador del sistema escribe ciertos scripts que recopilan y fusionan archivos de registro de diferentes servidores en un único archivo de registro y los colocan en servidores FTP para su descarga. Ejecutar combinaciones cat/grep/unig/sort en sesiones SSH paralelas. Esto es exactamente lo que hace Amazon AWS y puede informarle a su gerente. Utilice una herramienta como Graylog o ELK Stack (Elasticsearch, Logstash, Kibana)

¿Cómo se encuentran mis microservicios entre sí?

Hasta ahora, hemos asumido que nuestros microservicios se conocen entre sí y conocen el IPS correspondiente. Hablemos de configuración estática. Entonces, nuestro monolito bancario [ip = 192.168.200.1] sabe que necesita comunicarse con el servidor de riesgo [ip = 192.168.200.2], que está codificado en el archivo de propiedades. Sin embargo, puedes hacer las cosas más dinámicas:
  • Utilice un servidor de configuración basado en la nube desde el cual todos los microservicios extraen sus configuraciones en lugar de implementar archivos application.properties en sus microservicios.
  • Dado que sus instancias de servicio pueden cambiar dinámicamente su ubicación, vale la pena buscar servicios que sepan dónde residen sus servicios, cuáles son sus IP y cómo enrutarlos.
  • Ahora que todo es dinámico, surgen nuevos problemas, como la elección automática de un líder: ¿quién es el maestro que trabaja en determinadas tareas para no procesarlas dos veces, por ejemplo? ¿Quién reemplaza a un líder cuando fracasa? ¿Sobre qué base se produce el reemplazo?
En términos generales esto es lo que se llama orquestación de microservicios y es otro tema sin fondo. Bibliotecas como Eureka o Zookeeper intentan "resolver" estos problemas mostrando qué servicios están disponibles. Por otra parte, introducen una complejidad adicional. Pregúntele a cualquiera que alguna vez haya instalado ZooKeeper.

¿Cómo organizar la autorización y autenticación utilizando microservicios Java?

Este tema también merece una historia aparte. Nuevamente, las opciones van desde autenticación HTTPS básica codificada con marcos de seguridad personalizados hasta ejecutar una instalación de Oauth2 con su propio servidor de autorización.

¿Cómo puedo asegurarme de que todos mis entornos tengan el mismo aspecto?

Lo que es cierto para las implementaciones sin un microservicio también lo es para las implementaciones con uno. Pruebe una combinación de Docker/Testcontainers y Scripting/Ansible.

No hay duda: brevemente sobre YAML

Alejémonos de las bibliotecas y los problemas relacionados por un momento y echemos un vistazo rápido a Yaml. Este formato de archivo se utiliza de facto como formato para "escribir configuración como código". También lo utilizan herramientas simples como Ansible y gigantes como Kubernetes. Para experimentar el dolor de la sangría YAML, intente escribir un archivo Ansible simple y vea cuánto tiene que editar el archivo antes de que funcione como se esperaba. ¡Y esto a pesar de que el formato es compatible con todos los principales IDE! Después de eso, regrese para terminar de leer esta guía.
Yaml:
  - is:
    - so
    - great

¿Qué pasa con las transacciones distribuidas? ¿Pruebas de rendimiento? ¿Otros temas?

Quizás algún día, en futuras ediciones del manual. Por ahora, eso es todo. ¡Quédate con nosotros!

Problemas conceptuales con los microservicios

Además de los problemas específicos de los microservicios en Java, hay otros problemas, digamos, que aparecen en cualquier proyecto de microservicios. Se relacionan principalmente con la organización, el equipo y la gestión.

No coinciden el frontend y el backend

La falta de coincidencia entre frontend y backend es un problema muy común en muchos proyectos de microservicios. ¿Qué significa? Sólo que en los viejos monolitos, los desarrolladores de interfaces web tenían una fuente específica para obtener datos. En proyectos de microservicios, los desarrolladores front-end de repente tienen n fuentes de las que obtener datos. Imagine que está creando algún tipo de proyecto de microservicios de IoT (Internet de las cosas) en Java. Digamos que gestiona máquinas geodésicas y hornos industriales en toda Europa. Y estos hornos le envían actualizaciones periódicas con sus temperaturas y cosas por el estilo. Tarde o temprano, es posible que desee encontrar hornos en la interfaz de usuario del administrador, tal vez utilizando microservicios de "búsqueda de hornos". Dependiendo de cuán estrictamente apliquen sus contrapartes de backend el diseño basado en dominios o las leyes de microservicio, el microservicio "buscar horno" solo puede devolver ID de horno y no otros datos como tipo, modelo o ubicación. Para hacer esto, los desarrolladores frontend deberán realizar una o n llamadas adicionales (según la implementación de paginación) en el microservicio "obtener datos del horno" con los ID que recibieron del primer microservicio. Una guía para los microservicios de Java.  Parte 3: preguntas generales - 4Y aunque este es sólo un ejemplo simple, aunque tomado de un proyecto real (!), demuestra el siguiente problema: los supermercados se han vuelto extremadamente populares. Eso es porque con ellos no tienes que ir a 10 lugares diferentes para comprar verduras, limonada, pizza congelada y papel higiénico. En lugar de eso, vas a un lugar, es más fácil y rápido. Lo mismo ocurre con los desarrolladores de microservicios y front-end.

Expectativas de gestión

La gerencia tiene la impresión errónea de que ahora necesitan contratar una cantidad infinita de desarrolladores para un proyecto (general), ya que los desarrolladores ahora pueden trabajar de manera completamente independiente unos de otros, cada uno en su propio microservicio. Sólo se requiere un pequeño trabajo de integración al final (poco antes del lanzamiento). Una guía para los microservicios de Java.  Parte 3: preguntas generales - 5De hecho, este enfoque es extremadamente problemático. En los siguientes párrafos intentaremos explicar por qué.

“Piezas más pequeñas” no equivalen a “piezas mejores”

Sería un gran error suponer que un código dividido en 20 partes será necesariamente de mayor calidad que una sola pieza. Incluso si tomamos la calidad desde un punto de vista puramente técnico, es posible que nuestros servicios individuales aún estén ejecutando 400 consultas de Hibernate para seleccionar un usuario de la base de datos, atravesando capas de código no compatible. Una vez más, volvemos a la cita de Simon Brown: si no se construyen monolitos correctamente, será difícil construir microservicios adecuados. A menudo es muy tarde para hablar de tolerancia a fallos en proyectos de microservicios. Tanto es así que a veces da miedo ver cómo funcionan los microservicios en proyectos reales. La razón de esto es que los desarrolladores de Java no siempre están preparados para estudiar la tolerancia a fallos, las redes y otros temas relacionados al nivel adecuado. Las “piezas” en sí son más pequeñas, pero las “partes técnicas” son más grandes. Imagine que a su equipo de microservicios se le pide que escriba un microservicio técnico para iniciar sesión en un sistema de base de datos, algo como esto:
@Controller
class LoginController {
    // ...
    @PostMapping("/login")
    public boolean login(String username, String password) {
        User user = userDao.findByUserName(username);
        if (user == null) {
            // обработка варианта с несуществующим пользователем
            return false;
        }
        if (!user.getPassword().equals(hashed(password))) {
            // обработка неверного пароля
            return false;
        }
        // 'Ю-ху, залогинoсь!';
        // установите cookies, делайте, что угодно
        return true;
    }
}
Ahora su equipo puede decidir (y tal vez incluso convencer a los empresarios) que todo esto es demasiado simple y aburrido, en lugar de escribir un servicio de inicio de sesión, es mejor escribir un microservicio UserStateChanged realmente útil sin ningún requisito real y tangible para el negocio. Y dado que actualmente algunas personas tratan a Java como a un dinosaurio, escribamos nuestro microservicio UserStateChanged en el moderno Erlang. E intentemos usar árboles rojo-negros en alguna parte, porque Steve Yegge escribió que hay que conocerlos al dedillo para postularse a Google. Desde una perspectiva de integración, mantenimiento y diseño general, esto es tan malo como escribir capas de código espagueti dentro de un solo monolito. ¿Un ejemplo artificial y ordinario? Esto es cierto. Sin embargo, esto puede suceder en la realidad.

Menos piezas, menos comprensión

Entonces, naturalmente, surge la pregunta de comprender el sistema como un todo, sus procesos y flujos de trabajo, pero al mismo tiempo usted, como desarrollador, solo es responsable de trabajar en su microservicio aislado [95: login-101: updateUserProfile]. Armoniza con el párrafo anterior, pero dependiendo de su organización, nivel de confianza y comunicación, esto puede generar mucha confusión, encogimiento de hombros y culpas si hay una falla accidental en la cadena de microservicios. Y no hay nadie que asuma toda la responsabilidad por lo sucedido. Y no se trata en absoluto de deshonestidad. De hecho, es muy difícil conectar diferentes partes y comprender su lugar en el panorama general del proyecto.

Comunicaciones y servicio.

El nivel de comunicación y servicio varía mucho según el tamaño de la empresa. Sin embargo, la relación general es obvia: cuanto más, más problemático.
  • ¿Quién ejecuta el microservicio n.º 47?
  • ¿Acaban de implementar una versión nueva e incompatible de un microservicio? ¿Dónde se documentó esto?
  • ¿Con quién debo hablar para solicitar una nueva función?
  • ¿Quién soportará ese microservicio en Erlang, después de que el único que conocía este idioma dejara la empresa?
  • ¡Todos nuestros equipos de microservicios trabajan no solo en diferentes lenguajes de programación, sino también en diferentes zonas horarias! ¿Cómo coordinamos todo esto correctamente?
Una guía para los microservicios de Java.  Parte 3: preguntas generales - 6El punto principal es que, al igual que con DevOps, un enfoque completo de microservicios en una empresa grande, tal vez incluso internacional, conlleva una serie de problemas de comunicación adicionales. Y la empresa debe prepararse seriamente para ello.

conclusiones

Después de leer este artículo, puede pensar que el autor es un ferviente oponente de los microservicios. Esto no es del todo cierto: básicamente intento resaltar puntos a los que pocas personas prestan atención en la loca carrera por las nuevas tecnologías.

¿Microservicios o monolito?

El uso de microservicios Java en cualquier momento y lugar es un extremo. El otro resulta ser algo así como cientos de viejos módulos Maven en un monolito. Tu tarea es encontrar el equilibrio adecuado. Esto es especialmente cierto para los nuevos proyectos. No hay nada que le impida adoptar un enfoque más conservador y "monolítico" y crear menos módulos Maven buenos, en lugar de comenzar con veinte microservicios listos para la nube.

Los microservicios generan complejidad adicional

Tenga en cuenta que cuantos más microservicios tenga y menos DevOps realmente potentes tenga (no, ejecutar un par de scripts de Ansible o implementar en Heroku no cuenta), más problemas tendrá más adelante en el proceso. Incluso leer hasta el final de la sección de esta guía dedicada a preguntas generales sobre los microservicios de Java es una tarea bastante tediosa. Piense detenidamente en implementar soluciones a todos estos problemas de infraestructura y de repente se dará cuenta de que ya no se trata de programación empresarial (lo que le pagan por hacer) sino de arreglar más tecnología en aún más tecnología. Shiva Prasad Reddy lo resumió perfectamente en su blog : “No tienes idea de lo terrible que es cuando un equipo pasa el 70% del tiempo luchando con esta infraestructura moderna y solo le queda el 30% del tiempo para hacer la lógica empresarial real. ” Shiva Prasad Reddy

¿Vale la pena crear microservicios Java?

Para responder a esta pregunta, me gustaría finalizar este artículo con un adelanto muy atrevido, parecido a una entrevista de Google. Si conoce la respuesta a esta pregunta por experiencia, incluso si no parece tener nada que ver con los microservicios, es posible que esté preparado para un enfoque de microservicios.

Guión

Imagine que tiene un monolito de Java ejecutándose solo en el servidor dedicado más pequeño de Hetzner . Lo mismo se aplica a su servidor de base de datos, también se ejecuta en una máquina Hetzner similar . Y supongamos también que su monolito Java puede manejar flujos de trabajo, por ejemplo, el registro de usuarios, y que no está creando cientos de consultas de bases de datos por flujo de trabajo, sino un número más razonable (<10).

Pregunta

¿Cuántas conexiones de base de datos debe abrir su monolito Java (grupo de conexiones) en su servidor de base de datos? ¿Porqué es eso? ¿Cuántos usuarios activos simultáneos crees que tu monolito puede escalar (aproximadamente)?

Respuesta

Deja tu respuesta a estas preguntas en la sección de comentarios. Espero todas las respuestas. Una guía para los microservicios de Java.  Parte 3: preguntas generales - 8Ahora decídete. Si lees hasta el final, ¡te estamos muy agradecidos!
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION