Transacciones en REST?


147

Me pregunto cómo implementaría el siguiente caso de uso en REST. ¿Es posible hacerlo sin comprometer el modelo conceptual?

Lea o actualice múltiples recursos dentro del alcance de una sola transacción. Por ejemplo, transfiera $ 100 de la cuenta bancaria de Bob a la cuenta de John.

Por lo que puedo decir, la única forma de implementar esto es haciendo trampa. Puede PUBLICAR en el recurso asociado con John o Bob y llevar a cabo toda la operación utilizando una sola transacción. En lo que a mí respecta, esto rompe la arquitectura REST porque esencialmente está haciendo un túnel de una llamada RPC a través de POST en lugar de realmente operar en recursos individuales.

Respuestas:


91

Considere un escenario de cesta de la compra RESTful. La cesta de la compra es conceptualmente su envoltorio de transacciones. De la misma manera que puede agregar varios artículos a una cesta de la compra y luego enviar esa cesta para procesar el pedido, puede agregar la entrada de la cuenta de Bob al contenedor de transacciones y luego la entrada de la cuenta de Bill al contenedor. Cuando todas las piezas están en su lugar, puede POSTAR / PONER el contenedor de transacciones con todas las piezas componentes.


18
¿Por qué TransferMoneyTransaction no sería un recurso bancario viable?
Darrel Miller,

8
Si se asegura de que sus puntos finales se refieren a sustantivos, generalmente es intuitivo lo que los verbos estándar GET, PUT, POST, DELETE le harán a ese sustantivo. RPC permite que los puntos finales sean verbos y, por lo tanto, pueden entrar en conflicto con los verbos HTTP y la intención se vuelve confusa.
Darrel Miller

10
por ejemplo, ¿qué sucede si hace un DELETE HTTP en el punto final UpdateXYZ? ¿Elimina XYZ? ¿Elimina la actualización o simplemente hace una actualización e ignora la eliminación del verbo HTTP? Al mantener los verbos fuera del punto final, se elimina la confusión.
Darrel Miller

55
¿Y qué pasa con las transacciones a través de múltiples servicios? y qué pasa cuando desea hacer un conjunto de cambios 'no relacionados' que el servicio no expone ningún contenedor de transacciones implícitas ... además, ¿por qué tener un tipo de transacción específico cuando pasamos a transacciones de propósito general que no están relacionadas con sus datos reales? cambios Es posible que las transacciones no coincidan con las de reposo, pero parece que las transacciones se deben superponer en capas, sin relación con las llamadas de descanso, aparte del hecho de que los encabezados de solicitud contendrían una referencia de transacción.
meandmycode

44
@meandmycode Las transacciones de la base de datos deben colocarse detrás de una interfaz REST. Alternativamente, puede exponer una transacción comercial (no una transacción de base de datos) como un recurso en sí mismo y luego debe tomar medidas compensatorias en caso de falla.
Darrel Miller

60

Hay algunos casos importantes que no se responden con esta pregunta, lo que creo que es una lástima, porque tiene una clasificación alta en Google para los términos de búsqueda :-)

Específicamente, una buena propiedad sería: si PUBLICA dos veces (porque algo de caché tiene hipo en el intermedio), no debe transferir la cantidad dos veces.

Para llegar a esto, crea una transacción como un objeto. Esto podría contener todos los datos que ya conoce y poner la transacción en un estado pendiente.

POST /transfer/txn
{"source":"john's account", "destination":"bob's account", "amount":10}

{"id":"/transfer/txn/12345", "state":"pending", "source":...}

Una vez que tenga esta transacción, puede confirmarla, algo como:

PUT /transfer/txn/12345
{"id":"/transfer/txn/12345", "state":"committed", ...}

{"id":"/transfer/txn/12345", "state":"committed", ...}

Tenga en cuenta que las posiciones múltiples no importan en este punto; incluso un GET en el txn devolvería el estado actual. Específicamente, el segundo PUT detectaría que el primero ya estaba en el estado apropiado y simplemente lo devolvería, o, si intenta ponerlo en el estado "revertido" después de que ya esté en el estado "comprometido", obtendrá un error, y la transacción confirmada real de vuelta.

Siempre que hable con una sola base de datos, o una base de datos con un monitor de transacciones integrado, este mecanismo realmente funcionará bien. También puede introducir tiempos de espera para las transacciones, que incluso podría expresar utilizando encabezados de Expires si lo desea.


Interesante discusión! Me gustaría agregar que la publicación inicial debe realizarse en un solo paso. No se puede agregar más tarde (entonces estamos en territorio de carritos de compras y los carritos de compras tienen muchos controles y saldos para evitar que causen daños al usuario final, incluso la legislación, las transferencias bancarias no) ...
Erk

33

En términos REST, los recursos son sustantivos sobre los que se puede actuar con verbos CRUD (crear / leer / actualizar / eliminar). Dado que no hay un verbo "transferir dinero", necesitamos definir un recurso de "transacción" que se pueda actuar con CRUD. Aquí hay un ejemplo en HTTP + POX. El primer paso es CREAR (método HTTP POST) una nueva transacción vacía :

POST /transaction

Esto devuelve una ID de transacción, por ejemplo, "1234" y según la URL "/ transacción / 1234". Tenga en cuenta que disparar esta POST varias veces no creará la misma transacción con múltiples ID y también evita la introducción de un estado "pendiente". Además, POST no siempre puede ser idempotente (un requisito REST), por lo que generalmente es una buena práctica minimizar los datos en POST.

Puede dejar la generación de un ID de transacción al cliente. En este caso, POST / transacción / 1234 crearía la transacción "1234" y el servidor devolvería un error si ya existía. En la respuesta de error, el servidor podría devolver una ID actualmente no utilizada con una URL apropiada. No es una buena idea consultar al servidor para obtener una nueva ID con un método GET, ya que GET nunca debería alterar el estado del servidor, y crear / reservar una nueva ID alteraría el estado del servidor.

A continuación, ACTUALIZAMOS (método PUT HTTP) la transacción con todos los datos, comprometiéndolos implícitamente:

PUT /transaction/1234
<transaction>
  <from>/account/john</from>
  <to>/account/bob</to>
  <amount>100</amount>
</transaction>

Si una transacción con ID "1234" ha sido PUT anteriormente, el servidor da una respuesta de error, de lo contrario, una respuesta OK y una URL para ver la transacción completada.

NB: en / account / john, "john" realmente debería ser el número de cuenta único de John.


44
Igualar REST con CRUD es un grave error. POST no tiene que significar CREAR.

12
¿Grave error? Sé que hay diferencias entre PUT y POST, pero hay un mapeo suelto a CRUD. "Seriamente"?
Ted Johnson

3
Sí, en serio. CRUD es una forma de estructurar el almacenamiento de datos; REST es una forma de estructurar el flujo de datos de la aplicación. Puede hacer CRUD en REST, pero no puede hacer REST en CRUD. No son equivalentes.
Jon Watte

20

Gran pregunta, REST se explica principalmente con ejemplos similares a bases de datos, donde algo se almacena, actualiza, recupera y elimina. Hay pocos ejemplos como este, donde se supone que el servidor procesa los datos de alguna manera. No creo que Roy Fielding haya incluido ninguno en su tesis, que se basó en http después de todo.

Pero sí habla de la "transferencia de estado de representación" como una máquina de estado, con enlaces que se mueven al siguiente estado. De esta manera, los documentos (las representaciones) realizan un seguimiento del estado del cliente, en lugar de que el servidor tenga que hacerlo. De esta manera, no hay estado del cliente, solo el estado en términos de en qué enlace está.

He estado pensando en esto, y me parece razonable que para que el servidor procese algo por usted, cuando carga, el servidor creará automáticamente recursos relacionados y le dará los enlaces a ellos (de hecho, no no es necesario crearlos automáticamente: solo podría decirle los enlaces, y solo los creará cuando y si los sigue: creación perezosa). Y también para darle enlaces para crear nuevos recursos relacionados: un recurso relacionado tiene el mismo URI pero es más largo (agrega un sufijo). Por ejemplo:

  1. Sube ( POST ) la representación del concepto de una transacción con toda la información. Esto se parece a una llamada RPC, pero realmente está creando el "recurso de transacción propuesto". por ejemplo, URI: los /transaction fallos ocasionarán la creación de múltiples recursos, cada uno con un URI diferente.
  2. La respuesta del servidor indica el URI del recurso creado, su representación; esto incluye el enlace ( URI ) para crear el recurso relacionado de un nuevo "recurso de transacción comprometido". Otros recursos relacionados son el enlace para eliminar la transacción propuesta. Estos son estados en la máquina de estado, que el cliente puede seguir. Lógicamente, estos son parte del recurso que se ha creado en el servidor, más allá de la información que proporcionó el cliente. por ejemplo URIs: /transaction/1234/proposed, /transaction/1234/committed
  3. Usted POSTAL al enlace para crear el "recurso transacción comprometida" , lo que crea ese recurso, cambiando el estado del servidor (los saldos de las cuentas de dos) **. Por su naturaleza, este recurso solo se puede crear una vez y no se puede actualizar. Por lo tanto, no pueden ocurrir fallas técnicas que comprometan muchas transacciones.
  4. Puede OBTENER esos dos recursos, para ver cuál es su estado. Suponiendo que un POST puede cambiar otros recursos, la propuesta ahora se marcaría como "comprometida" (o tal vez, no está disponible en absoluto).

Esto es similar a cómo funcionan las páginas web, con la página final diciendo "¿estás seguro de que quieres hacer esto?" Esa página web final es en sí misma una representación del estado de la transacción, que incluye un enlace para ir al siguiente estado. No solo transacciones financieras; también (por ejemplo) vista previa y luego confirmar en wikipedia. Supongo que la distinción en REST es que cada etapa en la secuencia de estados tiene un nombre explícito (su URI).

En las transacciones / ventas de la vida real, a menudo hay diferentes documentos físicos para las diferentes etapas de una transacción (propuesta, orden de compra, recibo, etc.). Aún más por comprar una casa, con liquidación, etc.

OTOH Esto se siente como jugar con la semántica para mí; Me incomoda la nominalización de convertir verbos en sustantivos para que sea RESTful, "porque usa sustantivos (URI) en lugar de verbos (llamadas RPC)". es decir, el sustantivo "recurso de transacción comprometida" en lugar del verbo "comprometer esta transacción". Supongo que una ventaja de la nominalización es que puede referirse al recurso por su nombre, en lugar de tener que especificarlo de alguna otra manera (como mantener el estado de la sesión, para saber qué es "esta" transacción ...)

Pero la pregunta importante es: ¿Cuáles son los beneficios de este enfoque? es decir, ¿de qué manera es mejor este estilo REST que el estilo RPC? ¿Es una técnica excelente para páginas web también útil para procesar información, más allá de almacenar / recuperar / actualizar / eliminar? Creo que el beneficio clave de REST es la escalabilidad; Un aspecto de esto no es la necesidad de mantener el estado del cliente explícitamente (sino hacerlo implícito en el URI del recurso, y los siguientes estados como enlaces en su representación). En ese sentido, ayuda. ¿Quizás esto también ayuda en la estratificación / canalización? OTOH solo un usuario mirará su transacción específica, por lo que no hay ninguna ventaja en almacenarla en caché para que otros puedan leerla, la gran victoria para http.


¿Podría explicar cómo "la no necesidad de mantener el estado en el cliente" ayuda a la escalabilidad? ¿Qué tipo de escalabilidad? Escalabilidad en qué sentido?
jhegedus

11

Si retrocede para resumir la discusión aquí, está bastante claro que REST no es apropiado para muchas API, particularmente cuando la interacción cliente-servidor es intrínsecamente dinámica, como lo es con las transacciones no triviales. ¿Por qué pasar por todos los aros sugeridos, tanto para el cliente como para el servidor, a fin de seguir pedantemente algún principio que no se ajuste al problema? Un mejor principio es dar al cliente la forma más fácil, más natural y productiva de componer la aplicación.

En resumen, si realmente está haciendo muchas transacciones (tipos, no instancias) en su aplicación, realmente no debería crear una API RESTful.


9
Correcto, pero ¿cuál debería ser una alternativa en caso de arquitectura de microservicio distribuido?
Vitamon

11

Me he alejado de este tema durante 10 años. Volviendo, no puedo creer la religión que se hace pasar por ciencia en la que te sumerges cuando buscas en Google + confiable. La confusión es mítica.

Yo dividiría esta amplia pregunta en tres:

  • Servicios aguas abajo. Cualquier servicio web que desarrolle tendrá servicios posteriores que utilizará, y cuya sintaxis de transacción no tiene más remedio que seguir. Debe intentar ocultar todo esto a los usuarios de su servicio, y asegurarse de que todas las partes de su operación tengan éxito o fracasen como grupo, y luego devolver este resultado a sus usuarios.
  • Sus servicios Los clientes desean resultados inequívocos en las llamadas al servicio web, y el patrón REST habitual de realizar solicitudes POST, PUT o DELETE directamente sobre recursos sustantivos me parece una forma pobre y fácil de proporcionar esta certeza. Si le preocupa la confiabilidad, debe identificar las solicitudes de acción. Esta identificación puede ser una guía creada en el cliente, o un valor inicial de una base de datos relacional en el servidor, no importa. Para las ID generadas por el servidor, use una solicitud-respuesta 'previa al vuelo' para intercambiar la identificación de la acción. Si esta solicitud falla o la mitad tiene éxito, no hay problema, el cliente simplemente repite la solicitud. Los identificadores no utilizados no causan daño.

    Esto es importante porque permite que todas las solicitudes posteriores sean totalmente idempotentes, en el sentido de que si se repiten n veces devuelven el mismo resultado y no hacen que suceda nada más. El servidor almacena todas las respuestas contra la identificación de la acción y, si ve la misma solicitud, reproduce la misma respuesta. Un tratamiento más completo del patrón se encuentra en este documento de google . El documento sugiere una implementación que, creo (!), Sigue ampliamente los principios de REST. Los expertos seguramente me dirán cómo viola a otros. Este patrón puede ser útil para cualquier llamada insegura a su servicio web, ya sea que haya o no transacciones posteriores involucradas.
  • Integración de su servicio en "transacciones" controladas por servicios ascendentes. En el contexto de los servicios web, se considera que las transacciones completas de ACID generalmente no valen la pena, pero puede ayudar mucho a los consumidores de su servicio al proporcionar enlaces de cancelación y / o confirmación en su respuesta de confirmación, y así lograr transacciones por compensación .

Su requerimiento es fundamental. No dejes que la gente te diga que tu solución no es kosher. Juzgue sus arquitecturas a la luz de qué tan bien y cómo simplemente abordan su problema.


9

Tendría que rodar su propio tipo de "identificación de transacción" de gestión de tx. Entonces serían 4 llamadas:

http://service/transaction (some sort of tx request)
http://service/bankaccount/bob (give tx id)
http://service/bankaccount/john (give tx id)
http://service/transaction (request to commit)

Tendría que manejar el almacenamiento de las acciones en una base de datos (si la carga está equilibrada) o en la memoria o tal, luego manejar la confirmación, la reversión y el tiempo de espera.

No es realmente un día de descanso en el parque.


44
No creo que esta sea una buena ilustración. Solo necesita dos pasos: Crear transacción (crea una transacción en estado "pendiente") y Confirmar transacción (se confirma si no está comprometida y mueve el recurso al estado comprometido o retrotraído).
Jon Watte

2

Creo que en este caso es totalmente aceptable romper la teoría pura de REST en esta situación. En cualquier caso, no creo que haya nada realmente en REST que diga que no puede tocar objetos dependientes en casos comerciales que lo requieran.

Realmente creo que no vale la pena los aros adicionales por los que pasaría para crear un administrador de transacciones personalizado, cuando simplemente podría aprovechar la base de datos para hacerlo.


2

En primer lugar, transferir dinero no es nada que no pueda hacer en una sola llamada de recursos. La acción que quieres hacer es enviar dinero. Entonces agrega un recurso de transferencia de dinero a la cuenta del remitente.

POST: accounts/alice, new Transfer {target:"BOB", abmount:100, currency:"CHF"}.

Hecho. No necesita saber que esta es una transacción que debe ser atómica, etc. Simplemente transfiere dinero, también conocido como. enviar dinero de A a B.


Pero para los casos raros aquí una solución general:

Si desea hacer algo muy complejo que involucre muchos recursos en un contexto definido con muchas restricciones que realmente crucen la barrera de qué versus por qué (conocimiento de negocio versus implementación) necesita transferir el estado. Como REST no debería tener estado, usted como cliente debe transferir el estado.

Si transfiere el estado, debe ocultar la información del cliente. El cliente no debe conocer la información interna que solo necesita la implementación, pero no lleva información relevante en términos de negocio. Si esa información no tiene valor comercial, el estado debe estar encriptado y debe usarse una metáfora como token, pase o algo.

De esta manera, se puede pasar el estado interno y usar cifrado y firmar el sistema puede ser seguro y sólido. Encontrar la abstracción correcta para el cliente por qué pasa la información del estado es algo que depende del diseño y la arquitectura.


La verdadera solución:

Recuerde que REST está hablando de HTTP y HTTP viene con el concepto de usar cookies. Esas cookies a menudo se olvidan cuando las personas hablan sobre API REST y flujos de trabajo e interacciones que abarcan múltiples recursos o solicitudes.

Recuerde lo que está escrito en Wikipedia sobre las cookies HTTP:

Las cookies fueron diseñadas para ser un mecanismo confiable para que los sitios web recuerden información con estado (como artículos en un carrito de compras) o para registrar la actividad de navegación del usuario (incluido hacer clic en botones particulares, iniciar sesión o registrar qué páginas visitó el usuario hasta el momento atrás como hace meses o años).

Básicamente, si necesita pasar el estado, use una cookie. Está diseñado exactamente por la misma razón, es HTTP y, por lo tanto, es compatible con REST por diseño :).


La mejor solución:

Si habla de un cliente que realiza un flujo de trabajo que involucra múltiples solicitudes, generalmente habla del protocolo. Cada forma de protocolo viene con un conjunto de condiciones previas para cada paso potencial, como realizar el paso A antes de poder hacer B.

Esto es natural, pero exponer el protocolo a los clientes hace que todo sea más complejo. Para evitarlo, solo piense en lo que hacemos cuando tenemos que hacer interacciones complejas y cosas en el mundo real ... Usamos un agente.

Usando la metáfora del Agente, puede proporcionar un recurso que puede realizar todos los pasos necesarios para usted y almacenar la asignación / instrucciones reales sobre las que está actuando en su lista (para que podamos usar POST en el agente o una 'agencia').

Un ejemplo complejo:

Comprando una casa:

Debe demostrar su credibilidad (como proporcionar sus registros policiales), debe garantizar los detalles financieros, debe comprar la casa real con un abogado y un tercero de confianza que almacene los fondos, verificar que la casa ahora le pertenece a usted y agregue las cosas de compra a sus registros de impuestos, etc. (solo como ejemplo, algunos pasos pueden ser incorrectos o lo que sea).

Estos pasos pueden tardar varios días en completarse, algunos se pueden hacer en paralelo, etc.

Para hacer esto, solo le da al agente la tarea de comprar una casa como:

POST: agency.com/ { task: "buy house", target:"link:toHouse", credibilities:"IamMe"}.

Hecho. La agencia le devuelve una referencia que puede usar para ver y rastrear el estado de este trabajo y el resto lo hacen automáticamente los agentes de la agencia.

Piense en un rastreador de errores, por ejemplo. Básicamente, informa el error y puede usar la identificación del error para verificar qué está sucediendo. Incluso puede usar un servicio para escuchar los cambios de este recurso. Misión cumplida.


1

No debe usar transacciones del lado del servidor en REST.

Una de las restricciones REST:

Apátrida

La comunicación cliente-servidor se ve limitada aún más por el hecho de que no se almacena ningún contexto de cliente en el servidor entre solicitudes. Cada solicitud de cualquier cliente contiene toda la información necesaria para atender la solicitud, y cualquier estado de sesión se mantiene en el cliente.

La única forma RESTful es crear un registro de rehacer la transacción y ponerlo en el estado del cliente. Con las solicitudes, el cliente envía el registro de rehacer y el servidor rehace la transacción y

  1. revierte la transacción pero proporciona un nuevo registro de rehacer transacción (un paso más)
  2. o finalmente completar la transacción.

Pero quizás sea más simple usar una tecnología basada en sesión de servidor que admita transacciones del lado del servidor.


La cita es de la entrada REST de Wikipedia. ¿Es esta la fuente real o Wikipedia lo obtuvo de alguna parte? ¿Quién puede decir cuál es el contexto del cliente y cuál es el contexto del servidor?
bbsimonbb

1

Creo que ese sería el caso de usar un identificador único generado en el cliente para garantizar que el problema de conexión no implique una duplicidad guardada por la API.

Creo que usar un campo GUID generado por el cliente junto con el objeto de transferencia y garantizar que el mismo GUID no se reinserte nuevamente sería una solución más simple para el asunto de la transferencia bancaria.

No conozca escenarios más complejos, como la reserva de múltiples boletos aéreos o micro arquitecturas.

Encontré un artículo sobre el tema, relatando las experiencias de tratar con la atomicidad de la transacción en los servicios RESTful .


0

En el caso simple (sin recursos distribuidos), podría considerar la transacción como un recurso, donde el acto de crearla alcanza el objetivo final.

Entonces, para transferir entre <url-base>/account/ay <url-base>/account/b, puede publicar lo siguiente en <url-base>/transfer.

<transferencia>
    <from> <url-base> / account / a </from>
    <to> <url-base> / account / b </to>
    <amount> 50 </amount>
</transfer>

Esto crearía un nuevo recurso de transferencia y devolvería la nueva url de la transferencia, por ejemplo <url-base>/transfer/256.

En el momento de la publicación exitosa, entonces, la transacción 'real' se lleva a cabo en el servidor, y la cantidad se elimina de una cuenta y se agrega a otra.

Sin embargo, esto no cubre una transacción distribuida (si, por ejemplo, 'a' se mantiene en un banco detrás de un servicio, y 'b' se mantiene en otro banco detrás de otro servicio), aparte de decir "intente expresar todo operaciones en formas que no requieren transacciones distribuidas ".


2
Si no puede "expresar todas las operaciones de manera que no requieran transacciones distribuidas", entonces realmente necesita una confirmación de dos fases. La mejor idea que pude encontrar para implementar el compromiso de dos fases en REST es rest.blueoxen.net/cgi-bin/wiki.pl?TwoPhaseCommit , lo que es importante no estropea el espacio de nombres de URL y permite que un compromiso de dos fases se superponga Limpie la semántica REST.
Phasmal

3
El otro problema con esta sugerencia es que, si un caché tiene hipo y POST dos veces, obtienes dos transferencias.
Jon Watte

Es cierto, en cuyo caso necesitaría tener un proceso de dos pasos: cree un recurso de "transferencia" con una URL única y luego agregue los detalles de la transferencia como parte de la confirmación (dos partes como se menciona en las otras respuestas). Por supuesto, esto podría expresarse como la creación de un recurso de "transacción" y luego agregarle una operación de "transferencia".
Phasmal

-3

Supongo que podría incluir el TAN en la URL / recurso:

  1. PUT / transacción para obtener la ID (por ejemplo, "1")
  2. [PUT, GET, POST, lo que sea] / 1 / account / bob
  3. [PUT, GET, POST, lo que sea] / 1 / cuenta / factura
  4. BORRAR / transacción con ID 1

Solo una idea.


Veo dos problemas con este enfoque: 1) Implica que no puede acceder a un recurso fuera de una transacción (aunque tal vez esto no sea un gran problema). 2) Ninguna de las respuestas hasta ahora ha tocado el hecho de que el servidor ya no tiene estado, aunque sospecho que no se puede hacer nada al respecto.
Gili

Bueno, / 1 / account / bob y / account / bob son solo dos recursos diferentes. :) Y RE: sin estado, implica que el recurso siempre está disponible y no depende de una solicitud previa. Ya que solicitó transacciones, sí, ese no es el caso. Pero, de nuevo, querías transacciones.
Hasta el

1
Si un cliente tiene que ensamblar URI, entonces su API no es RESTful.
aehlke

1
¡No puedo entenderlos, muchachos! Si trata una transacción como un recurso (como en el ejemplo anterior), simplemente deja de tratar la transacción en el sentido clásico y la utiliza de "la forma REST adecuada", lo que simplifica aún más la programación de procesos transaccionales. Por ejemplo, puede incluir un href para la transacción en sus respuestas para evitar el desplazamiento en el entorno del lado del servidor distribuido, todavía no tiene estado (es solo un recurso, ¿no?) Y puede implementar el mecanismo de transacción real de todos modos quieres (¿y si no tienes un DB en la parte de atrás?)
Matthias Hryniszak

1
De una forma u otra, si simplemente deja de pensar en SQL / SOAP y comienza a pensar en HTTP (como lo hace el navegador) todo se vuelve simple
Matthias Hryniszak,
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.