{"id":30778,"date":"2019-10-31T21:37:23","date_gmt":"2019-10-31T18:37:23","guid":{"rendered":"https:\/\/prohoster.info\/blog\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\/"},"modified":"2019-10-31T21:37:23","modified_gmt":"2019-10-31T18:37:23","slug":"opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","status":"publish","type":"post","link":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","title":{"rendered":"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>\u00bfQu\u00e9 puede llevar a una empresa tan grande como Lamoda, con un proceso afinado y decenas de servicios interrelacionados, a cambiar su enfoque de manera significativa? La motivaci\u00f3n puede ser muy diversa: desde cuestiones legislativas hasta el deseo de experimentar que caracteriza a todos los programadores.<\/p>\n<p>Pero eso no significa en absoluto que no se pueda contar con beneficios adicionales. Sergey Zaika explicar\u00e1 en qu\u00e9 se puede ganar espec\u00edficamente si se implementa un API orientado a eventos en Kafka,<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/users\/fewald\/\" class=\"user_link\">fewald<\/a><\/noindex>). Tambi\u00e9n habr\u00e1 historias sobre errores cometidos y descubrimientos interesantes; no puede haber experimentaci\u00f3n sin ellos.<\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/7ab959ab45ec5c6565b35b18b361c0ea.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\n<em>Descargo de responsabilidad: Este art\u00edculo se basa en los materiales de un meetup que Sergey realiz\u00f3 en noviembre de 2018 en HighLoad++. La experiencia viva de Lamoda trabajando con Kafka atrajo a la audiencia tanto como otras conferencias en la programaci\u00f3n. Nos parece un excelente ejemplo de que siempre se pueden y deben encontrar personas afines, y los organizadores de HighLoad++ seguir\u00e1n esforz\u00e1ndose por crear un ambiente que lo facilite.<\/em><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Sobre el proceso<\/h2>\n<p>\nLamoda es una gran plataforma de comercio electr\u00f3nico que cuenta con su propio centro de contacto, servicio de entrega (y muchos socios), estudio fotogr\u00e1fico, un enorme almac\u00e9n y todo esto opera con su propio software. Existen decenas de m\u00e9todos de pago, socios B2B que pueden utilizar parte o todos estos servicios y quieren conocer informaci\u00f3n actual sobre sus productos. Adem\u00e1s, Lamoda opera en tres pa\u00edses adem\u00e1s de Rusia, y all\u00ed todo funciona un poco diferente. En total, probablemente hay m\u00e1s de un centenar de maneras de configurar un nuevo pedido, que debe ser procesado de manera particular. Todo esto funciona a trav\u00e9s de decenas de servicios que a veces se comunican de manera no obvia. Tambi\u00e9n hay un sistema central cuya principal responsabilidad son los estados de los pedidos. Lo llamamos BOB, y yo trabajo con \u00e9l.<\/p>\n<h2>Herramienta de reembolso con API orientada a eventos <\/h2>\n<p>\nLa palabra orientada a eventos est\u00e1 bastante desgastada; m\u00e1s adelante definiremos en detalle a qu\u00e9 nos referimos con esto. Comenzar\u00e9 con el contexto en el que decidimos probar el enfoque de API orientada a eventos en Kafka. <\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/3bfdce47dd8420fc63d84645e76de647.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEn cualquier tienda, adem\u00e1s de los pedidos que los clientes pagan, hay momentos en que se requiere que la tienda devuelva dinero, porque el producto no le conven\u00eda al cliente. Este proceso relativamente corto implica confirmar la informaci\u00f3n, si es necesario, y transferir el dinero. <\/p>\n<p>Sin embargo, el proceso de devoluci\u00f3n se ha complicado debido a cambios en la legislaci\u00f3n, y hemos tenido que implementar un microservicio separado para ello.<\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/0e358df5476e7448976e0f1147a103fb.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nNuestra motivaci\u00f3n:<\/p>\n<ol>\n<li><strong>Ley FZ-54<\/strong>\u00a0\u2014 en resumen, la ley exige informar a la agencia fiscal sobre cada operaci\u00f3n monetaria, ya sea una devoluci\u00f3n o un ingreso, en un SLA bastante corto de unos minutos. Nosotros, como e-commerce, realizamos un gran n\u00famero de operaciones. T\u00e9cnicamente esto significa una nueva responsabilidad (y, por lo tanto, un nuevo servicio) y ajustes en todos los sistemas implicados.<\/li>\n<li><strong>BOB split<\/strong>\u00a0\u2014 es un proyecto interno de la empresa para liberar a BOB de un gran n\u00famero de responsabilidades no esenciales y reducir su complejidad general.<\/li>\n<\/ol>\n<p>\n<img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/d3ecf9961bdb372fc5f84ee9389f73ef.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEn este diagrama se muestran los principales sistemas de Lamoda. Actualmente, la mayor\u00eda de ellos son m\u00e1s bien <strong>un conjunto de 5-10 microservicios alrededor de un monolito en reducci\u00f3n<\/strong>. Est\u00e1n creciendo lentamente, pero tratamos de hacerlos menos numerosos, porque desplegar un fragmento aislado en medio es aterrador: no se puede permitir que falle. Todos los intercambios (las flechas) debemos reservarlos y asumir que cualquiera de ellos puede volverse inaccesible.<\/p>\n<p>En BOB tambi\u00e9n hay bastantes intercambios: sistemas de pago, entrega, notificaciones, etc. <\/p>\n<p>T\u00e9cnicamente, BOB es:<\/p>\n<ul>\n<li>~150k l\u00edneas de c\u00f3digo + ~100k l\u00edneas de pruebas;<\/li>\n<li>php7.2 + Zend 1 &amp; Symfony Components 3;<\/li>\n<li>&gt;100 API &amp; ~50 integraciones salientes;<\/li>\n<li>4 pa\u00edses con su l\u00f3gica de negocio. <\/li>\n<\/ul>\n<p>\nDesplegar BOB es costoso y doloroso, la cantidad de c\u00f3digo y las tareas que resuelve son tales que nadie puede retenerlo en su mente completamente. En general, hay muchas razones para simplificarlo.<\/p>\n<h2>Proceso de devoluci\u00f3n<\/h2>\n<p>\nInicialmente, el proceso involucra dos sistemas: BOB y Payment. Ahora aparecen otros dos:<\/p>\n<ul>\n<li>Fiscalization Service, que asumir\u00e1 los problemas de fiscalizaci\u00f3n y la comunicaci\u00f3n con los servicios externos.<\/li>\n<li>Refund Tool, donde simplemente se trasladan nuevos intercambios, para no inflar a BOB.<\/li>\n<\/ul>\n<p>\nAhora el proceso se ve as\u00ed:<\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/13c02975881ad35c61304053c604cda3.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<\/p>\n<ol>\n<li>BOB recibe una solicitud de reembolso.<\/li>\n<li>BOB informa de esto a Refund Tool.<\/li>\n<li>Refund Tool indica a Payment: \u00abDevuelve el dinero\u00bb.<\/li>\n<li>Payment devuelve el dinero.<\/li>\n<li>Refund Tool y BOB sincronizan sus estados entre s\u00ed, porque por ahora ambos lo necesitan. A\u00fan no estamos listos para cambiar completamente a Refund Tool, ya que en BOB hay una interfaz de usuario, informes para contabilidad, y en general muchos datos que no se transfieren tan f\u00e1cilmente. Tenemos que estar en dos lugares a la vez.<\/li>\n<li>Se env\u00eda una solicitud para fiscalizar.<\/li>\n<\/ol>\n<p>\nAl final, hemos creado un bus de eventos en Kafka, en el que todo est\u00e1 interconectado. Hurra, ahora tenemos un \u00fanico punto de falla (sarcasmo).<\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/674edd7972998b4985071f5250612c7e.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nLos pros y los contras son bastante evidentes. Hicimos el bus, por lo que ahora todos los servicios dependen de \u00e9l. Esto simplifica el dise\u00f1o, pero introduce un \u00fanico punto de falla en el sistema. Si Kafka cae, el proceso se detiene.<\/p>\n<h2>Qu\u00e9 es una API impulsada por eventos <\/h2>\n<p>\nUna buena respuesta a esta pregunta se encuentra en la presentaci\u00f3n de Martin Fowler (GOTO 2017) <noindex><a rel=\"nofollow\" href=\"https:\/\/youtu.be\/STKCRSUsyPO\">\u00abLos muchos significados de la arquitectura impulsada por eventos\u00bb<\/a><\/noindex>. <\/p>\n<p>En resumen, lo que hicimos:<\/p>\n<ol>\n<li>Enrollamos toda la comunicaci\u00f3n asincr\u00f3nica a trav\u00e9s de <strong>almacenamiento de eventos<\/strong>. En lugar de informar a cada consumidor interesado a trav\u00e9s de la red sobre el cambio de estado, escribimos en un repositorio centralizado un evento sobre el cambio de estado, y los consumidores interesados en el tema leen todo lo que aparece all\u00ed.<\/li>\n<li>Un evento en este caso es una notificaci\u00f3n (<strong>notifications<\/strong>) de que algo ha cambiado en alg\u00fan lugar. Por ejemplo, el estado de un pedido cambi\u00f3. Un consumidor que necesita algunos datos adicionales relacionados con el cambio de estado y que no est\u00e1n en la notificaci\u00f3n puede averiguar su estado por s\u00ed mismo.<\/li>\n<li>La opci\u00f3n m\u00e1xima es una fuente de eventos completa, <strong>transferencia de estado<\/strong>, en la que el evento contiene toda la informaci\u00f3n necesaria para el procesamiento: de d\u00f3nde y en qu\u00e9 estado pasaron, c\u00f3mo exactamente cambiaron los datos, etc. La \u00fanica cuesti\u00f3n es la viabilidad y el volumen de informaci\u00f3n que puede permitirse almacenar.<\/li>\n<\/ol>\n<p>\nEn el marco del lanzamiento de Refund Tool, utilizamos la tercera opci\u00f3n. Esto simplific\u00f3 el procesamiento de eventos, ya que no era necesario obtener informaci\u00f3n detallada, adem\u00e1s de que elimin\u00f3 el escenario en el que cada nuevo evento genera una oleada de solicitudes get de aclaraci\u00f3n de los consumidores.<\/p>\n<p>El servicio Refund Tool <strong>no est\u00e1 sobrecargado<\/strong>, por lo que Kafka all\u00ed es m\u00e1s una prueba que una necesidad. No creo que, si el servicio de reembolso se convirtiera en un proyecto de alta carga, la empresa estar\u00eda satisfecha.<\/p>\n<h4>Intercambio as\u00edncrono tal cual<\/h4>\n<p>\nPara intercambios as\u00edncronos, el departamento de PHP suele utilizar RabbitMQ. Reunimos datos para la solicitud, los colocamos en la cola, y el consumidor de ese mismo servicio los ley\u00f3 y los envi\u00f3 (o no los envi\u00f3). Para la propia API, Lamoda utiliza activamente Swagger. Dise\u00f1amos la API, la describimos en Swagger, generamos el c\u00f3digo del cliente y del servidor. Tambi\u00e9n utilizamos un JSON RPC 2.0 ligeramente ampliado. <\/p>\n<p>En algunos lugares se utilizan buses ESB, algunos operan con ActiveMQ, pero en general, <strong>RabbitMQ \u2014 est\u00e1ndar<\/strong>.<\/p>\n<h4>Intercambio as\u00edncrono A SER<\/h4>\n<p>\nAl dise\u00f1ar el intercambio a trav\u00e9s del events-bus, se tiene una analog\u00eda. Describimos de manera similar el intercambio futuro de datos a trav\u00e9s de las descripciones de la estructura del evento. El formato YAML, la generaci\u00f3n de c\u00f3digo tuvo que hacerse nosotros mismos, el generador seg\u00fan la especificaci\u00f3n crea DTO y ense\u00f1a a los clientes y servidores a trabajar con ellos. La generaci\u00f3n se realiza en dos lenguajes \u2014 <strong>golang y php<\/strong>. Esto permite mantener las bibliotecas alineadas. El generador est\u00e1 escrito en golang, por lo que recibi\u00f3 el nombre de gogi.<\/p>\n<p>El event-sourcing en Kafka es algo t\u00edpico. Hay una soluci\u00f3n de la versi\u00f3n enterprise principal de Kafka Confluent, hay <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/zalando\/nakadi\">nakadi<\/a><\/noindex>, una soluci\u00f3n de nuestros \"hermanos\" en el dominio de Zalando. Nuestra <strong>motivaci\u00f3n para comenzar con vanilla Kafka<\/strong>\u00a0es mantener la soluci\u00f3n gratuita, mientras decidimos finalmente si la utilizaremos de manera generalizada, as\u00ed como dejarnos espacio para maniobras y mejoras: queremos soporte para nuestro <strong>JSON RPC 2.0<\/strong>, generadores para dos lenguajes y veremos qu\u00e9 m\u00e1s. <\/p>\n<p>Ir\u00f3nicamente, incluso en un caso tan afortunado, cuando hay un negocio similar a Zalando que ha hecho una soluci\u00f3n similar, no podemos utilizarlo de manera efectiva. <\/p>\n<p>Arquitect\u00f3nicamente, en el inicio, el patr\u00f3n es el siguiente: leemos directamente de Kafka, pero escribimos solo a trav\u00e9s del events-bus. Para la lectura en Kafka hay mucho listo: brokers, balanceadores y est\u00e1 m\u00e1s o menos preparado para el escalado horizontal, esto quer\u00eda conservarse. Sin embargo, quisimos envolver la escritura a trav\u00e9s de un Gateway, tambi\u00e9n conocido como Events-bus, y aqu\u00ed est\u00e1 el porqu\u00e9.<\/p>\n<h3>Events-bus<\/h3>\n<p>\nO autob\u00fas de eventos. Es simplemente un gateway http sin estado, que asume varios roles importantes:<\/p>\n<ul>\n<li><strong>Validaci\u00f3n de producci\u00f3n<\/strong>\u00a0\u2014 verificamos que los eventos cumplen con nuestra especificaci\u00f3n.<\/li>\n<li><strong>Sistema maestro de eventos<\/strong>, es decir, es el \u00fanico sistema principal en la empresa que responde a la pregunta de qu\u00e9 eventos con qu\u00e9 estructuras se consideran v\u00e1lidos. La validaci\u00f3n incluye simplemente tipos de datos y enums para una especificaci\u00f3n r\u00edgida del contenido. <\/li>\n<li><strong>Funci\u00f3n hash<\/strong> para el sharding \u2014 la estructura del mensaje Kafka es de clave-valor y el hash de la clave se utiliza para calcular d\u00f3nde colocar esto.<\/li>\n<\/ul>\n<p><\/p>\n<h3>Por qu\u00e9<\/h3>\n<p>\nTrabajamos en una gran empresa con un proceso bien establecido. \u00bfPor qu\u00e9 cambiar algo? <strong>Es un experimento<\/strong>, y esperamos obtener algunas ventajas.<\/p>\n<h4>Intercambios 1:n+1 (uno a muchos)<\/h4>\n<p>\nEs muy sencillo conectar nuevos consumidores al API con Kafka. <\/p>\n<p>Supongamos que tienes un directorio que necesitas mantener actualizado en varios sistemas a la vez (y en algunos nuevos). Antes inventamos un bundle que implementaba un set-API, y a la sistema maestra le comunic\u00e1bamos las direcciones de los consumidores. Ahora, la sistema maestra env\u00eda actualizaciones a un t\u00f3pico, y todos los interesados lo leen. Ha aparecido un nuevo sistema: lo hemos suscrito al t\u00f3pico. S\u00ed, tambi\u00e9n es un bundle, pero m\u00e1s sencillo.<\/p>\n<p>En el caso de refund-tool, que es una pieza de BOB, nos resulta conveniente mantenerlos sincronizados a trav\u00e9s de Kafka. Payment dice que el dinero ha sido devuelto: BOB y RT se enteran de esto, cambian sus estados, y el Servicio de Fiscalizaci\u00f3n se entera y emite el recibo.<\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/b01b22a333b58e87aeef0c52d40e6960.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nTenemos planes de hacer un \u00fanico Servicio de Notificaciones que avise al cliente sobre las novedades en su pedido\/devoluciones. Actualmente, esta responsabilidad est\u00e1 repartida entre sistemas. Nos bastar\u00e1 con ense\u00f1ar al Servicio de Notificaciones a captar informaci\u00f3n relevante de Kafka y a reaccionar a ella (y desactivar estas notificaciones en los dem\u00e1s sistemas). No se requerir\u00e1n intercambios directos nuevos.<\/p>\n<h4>Impulsado por datos<\/h4>\n<p>\nLa informaci\u00f3n entre sistemas se vuelve transparente, sin importar cu\u00e1n 'sangriento' sea tu 'enterprise' ni cu\u00e1n numeroso sea tu backlog. En Lamoda hay un departamento de An\u00e1lisis de Datos que recopila informaci\u00f3n de los sistemas y la transforma en un formato reutilizable, tanto para el negocio como para sistemas inteligentes. Kafka permite proporcionarles r\u00e1pidamente muchos datos y mantener este flujo de informaci\u00f3n actualizado.<\/p>\n<h4>Registro de replicaci\u00f3n<\/h4>\n<p>\nLos mensajes no desaparecen despu\u00e9s de ser le\u00eddos, como en RabbitMQ. Cuando un evento contiene suficiente informaci\u00f3n para el procesamiento, tenemos un historial de los \u00faltimos cambios en el objeto y, si se desea, la posibilidad de aplicar esos cambios.<\/p>\n<p>El tiempo de almacenamiento del registro de replicaci\u00f3n depende de la intensidad de las escrituras en este t\u00f3pico; Kafka permite configurar flexiblemente los l\u00edmites de tiempo de almacenamiento y en cuanto al volumen de datos. Para los t\u00f3picos intensivos, es importante que todos los consumidores puedan leer la informaci\u00f3n antes de que desaparezca, incluso en caso de un breve per\u00edodo de inactividad. Generalmente se logra almacenar datos por\u00a0<strong>unidades de d\u00edas<\/strong>, lo cual es suficiente para el soporte. <\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/0e08dd384155289123ebee96430c2370.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nA continuaci\u00f3n, un peque\u00f1o resumen de la documentaci\u00f3n, para aquellos que no est\u00e1n familiarizados con Kafka (la imagen tambi\u00e9n es de la documentaci\u00f3n)<\/p>\n<p>En AMQP hay colas: escribimos mensajes en la cola para el consumidor. Por lo general, una cola es procesada por un solo sistema con la misma l\u00f3gica de negocio. Si es necesario notificar a varios sistemas, se puede ense\u00f1ar a la aplicaci\u00f3n a escribir en varias colas o configurar un exchange con un mecanismo fanout, que las clona autom\u00e1ticamente.<\/p>\n<p>En Kafka hay una abstracci\u00f3n similar <em>tema<\/em>, en el que escribes mensajes, pero no desaparecen despu\u00e9s de ser le\u00eddos. Por defecto, al conectarte a Kafka, recibes todos los mensajes y hay la posibilidad de guardar la ubicaci\u00f3n donde te detuviste. Es decir, lees de manera secuencial, puedes no marcar el mensaje como le\u00eddo, pero guardar el id desde el cual luego continuar\u00e1s leyendo. El id donde te detuviste se llama offset, y el mecanismo es commit offset. <\/p>\n<p>Por lo tanto, se puede implementar l\u00f3gica diferente. Por ejemplo, tenemos BOB en 4 instancias para diferentes pa\u00edses: Lamoda est\u00e1 en Rusia, Kazajist\u00e1n, Ucrania y Bielorrusia. Dado que se despliegan por separado, tienen un poco de su propia configuraci\u00f3n y l\u00f3gica de negocio. Indicamos en el mensaje a qu\u00e9 pa\u00eds pertenece. Cada consumidor BOB en cada pa\u00eds lee con diferentes groupId, y si el mensaje no le corresponde, lo omite, es decir, comitea directamente offset +1. Si el mismo tema es le\u00eddo por nuestro Servicio de Pagos, lo hace con un grupo separado, por lo que los offsets no se cruzan.<\/p>\n<p><b>Requisitos para eventos:<\/b><\/p>\n<ul>\n<li><strong>Integridad de los datos. <\/strong>Me gustar\u00eda que el evento contuviera suficientes datos para poder procesarlo. <\/li>\n<\/ul>\n<p><\/p>\n<ul>\n<li><strong>Integralidad. <\/strong>Delegamos al Events-bus la verificaci\u00f3n de que el evento es consistente y que puede procesarlo.<\/li>\n<li><strong>El orden es importante. <\/strong>En el caso de devoluciones, tenemos que trabajar con el historial. Con las notificaciones, el orden no es importante si son notificaciones homog\u00e9neas, el email ser\u00e1 el mismo sin importar cu\u00e1l pedido lleg\u00f3 primero. En el caso de las devoluciones, hay un proceso claro; si se cambia el orden, surgir\u00e1n excepciones, no se crear\u00e1 o procesar\u00e1 el reembolso, y caeremos en otro estado.<\/li>\n<li><strong>Consistencia. <\/strong>Tenemos un almacenamiento y ahora, en lugar de API, estamos creando eventos. Necesitamos una forma de transmitir r\u00e1pida y econ\u00f3micamente informaci\u00f3n sobre nuevos eventos y cambios en los ya existentes a nuestros servicios. Esto se logra mediante una especificaci\u00f3n com\u00fan en un repositorio git separado y generadores de c\u00f3digo. Por lo tanto, los clientes y servidores en diferentes servicios est\u00e1n coordinados.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Kafka en Lamoda<\/h2>\n<p>\nTenemos tres instalaciones de Kafka: <\/p>\n<ol>\n<li>Logs;<\/li>\n<li>I+D;<\/li>\n<li>Bus de eventos.<\/li>\n<\/ol>\n<p>\nHoy solo hablaremos del \u00faltimo punto. En el bus de eventos no tenemos instalaciones muy grandes: 3 brokers (servidores) y solo 27 temas. Como regla general, un tema es un proceso. Pero este es un detalle sutil que ahora abordaremos.<\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/f398852689b31429cc97b4cbffcabab5.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nArriba est\u00e1 el gr\u00e1fico de rps. El proceso de reembolsos est\u00e1 marcado con una l\u00ednea turquesa (s\u00ed, esa que est\u00e1 en el eje X) y el proceso de actualizaci\u00f3n de contenido est\u00e1 marcado en rosa. <\/p>\n<p>El cat\u00e1logo de Lamoda contiene millones de productos, y los datos se actualizan constantemente. Algunas colecciones quedan fuera de moda y se lanzan nuevas en su reemplazo; nuevos modelos aparecen continuamente en el cat\u00e1logo. Intentamos predecir qu\u00e9 ser\u00e1 interesante para nuestros clientes ma\u00f1ana, por lo que constantemente compramos nuevas cosas, las fotografiamos y actualizamos nuestra vitrina. <\/p>\n<p>Los picos rosas son actualizaciones de productos, es decir, cambios en los productos. Se puede ver que el equipo estaba tomando muchas fotos, y luego, \u00a1zas! \u2014 se carg\u00f3 un mont\u00f3n de eventos.<\/p>\n<h2>Casos de uso de eventos de Lamoda<\/h2>\n<p>\nLa arquitectura construida la utilizamos para las siguientes operaciones:<\/p>\n<ul>\n<li><strong>Seguimiento de estados de devoluciones<\/strong>: call-to-action y seguimiento de estados de todos los sistemas involucrados. Pagos, estados, fiscalizaci\u00f3n, notificaciones. Aqu\u00ed hemos probado un enfoque, creamos herramientas, recopilamos todos los errores, escribimos documentaci\u00f3n y contamos a los colegas c\u00f3mo usarlas.<\/li>\n<li><strong>Actualizaci\u00f3n de fichas de productos: <\/strong>configuraci\u00f3n, metadatos y caracter\u00edsticas. Un sistema lee (el que muestra), y varios escriben.<\/li>\n<li><strong>Email, push y sms<\/strong>: el pedido se ha reunido, el pedido ha llegado, la devoluci\u00f3n ha sido aceptada, etc., hay muchos. <\/li>\n<li><strong>Inventario, actualizaci\u00f3n de stock<\/strong>\u00a0\u2014 actualizaci\u00f3n cuantitativa de art\u00edculos, solo n\u00fameros: llegada a stock, devoluci\u00f3n. Es necesario que todos los sistemas relacionados con la reserva de productos operen con datos lo m\u00e1s actualizados posible. Actualmente, el sistema de actualizaci\u00f3n de stock es bastante complejo; Kafka permitir\u00e1 simplificarlo.<\/li>\n<li><strong>An\u00e1lisis de datos<\/strong> (Departamento de I+D), herramientas de ML, an\u00e1lisis, estad\u00edsticas. Queremos que la informaci\u00f3n sea transparente, y para eso Kafka es una buena opci\u00f3n.<\/li>\n<\/ul>\n<p>\nAhora, pasemos a la parte m\u00e1s interesante sobre los golpes y los descubrimientos interesantes que han ocurrido en medio a\u00f1o.<\/p>\n<h2>Problemas de dise\u00f1o<\/h2>\n<p>\nSupongamos que queremos crear algo nuevo, por ejemplo, trasladar todo el proceso de entrega a Kafka. Actualmente, parte del proceso se implementa en el Order Processing en BOB. Tras la transmisi\u00f3n del pedido al servicio de entrega, movimiento al almac\u00e9n intermedio y dem\u00e1s, hay un modelo de estado. Hay un monolito entero, incluso dos, m\u00e1s un mont\u00f3n de API dedicadas a la entrega. Ellos saben mucho m\u00e1s sobre la entrega. <\/p>\n<p>Parece que estas son \u00e1reas similares, pero para el Order Processing en BOB y para el sistema de entrega, los estados son diferentes. Por ejemplo, algunos servicios de mensajer\u00eda no env\u00edan estados intermedios, solo finales: 'entregado' o 'perdido'. Otros, en cambio, informan detalladamente sobre el movimiento del producto. Todos tienen sus propias reglas de validaci\u00f3n: para algunos, si el correo electr\u00f3nico es v\u00e1lido, entonces lo procesar\u00e1n; para otros, es no v\u00e1lido, pero el pedido se procesar\u00e1 de todos modos porque hay un tel\u00e9fono para contacto, y algunos dir\u00e1n que tal pedido no se procesar\u00e1 en absoluto.<\/p>\n<h3>Flujo de datos<\/h3>\n<p>\nEn el caso de Kafka surge la cuesti\u00f3n de la organizaci\u00f3n del flujo de datos. Esta tarea est\u00e1 relacionada con la elecci\u00f3n de estrategias en varios puntos, vamos a revisarlos todos.<\/p>\n<h4>\u00bfEn un topic o en varios?<\/h4>\n<p>\nTenemos una especificaci\u00f3n del evento. En BOB escribimos que un pedido determinado debe ser entregado, y especificamos: n\u00famero de pedido, su contenido, algunos SKU y c\u00f3digos de barras, etc. Cuando el producto llegue al almac\u00e9n, la entrega podr\u00e1 recibir estados, timestamps y todo lo necesario. Pero luego queremos recibir actualizaciones sobre estos datos en BOB. Se plantea un proceso inverso de obtenci\u00f3n de datos de la entrega. \u00bfEs el mismo evento? \u00bfO es un intercambio separado que merece un topic separado?<\/p>\n<p>Lo m\u00e1s probable es que sean muy similares, y la tentaci\u00f3n de crear un solo topic es comprensible, porque un topic separado significa consumidores separados, configuraciones separadas, generaci\u00f3n separada de todo esto. Pero no es un hecho.<\/p>\n<h4>\u00bfUn nuevo campo o un nuevo evento?<\/h4>\n<p>\nPero si utilizamos los mismos eventos, surge otro problema. Por ejemplo, no todos los sistemas de entrega pueden generar un DTO que pueda generar BOB. Les enviamos un id, pero ellos no lo guardan, porque no lo necesitan, y desde el punto de vista del inicio del proceso de event-bus, este campo es obligatorio. <\/p>\n<p>Si establecemos para el event-bus que este campo es obligatorio, nos vemos obligados a introducir reglas adicionales de validaci\u00f3n en BOB o en el manejador del evento inicial. La validaci\u00f3n comienza a proliferar por el servicio, lo que no es muy conveniente.<\/p>\n<p>Otro problema es la tentaci\u00f3n del desarrollo incremental. Nos dicen que necesitamos a\u00f1adir algo al evento, y, tal vez, si lo pensamos bien, deber\u00eda haber sido un evento separado. Pero en nuestro esquema, un evento separado es un t\u00f3pico separado. Un t\u00f3pico separado implica todo el proceso que describ\u00ed anteriormente. El desarrollador se siente tentado a simplemente agregar otro campo al esquema JSON y regenerar.<\/p>\n<p>En el caso de refunds, en seis meses llegamos a un evento de eventos. Ten\u00edamos un metaevento llamado refund update, que inclu\u00eda un campo type, describiendo en qu\u00e9 consist\u00eda realmente esta actualizaci\u00f3n. A partir de eso, ten\u00edamos \"hermosos\" switches con validadores que indicaban c\u00f3mo se deb\u00eda validar este evento con este type.<\/p>\n<h4>Versionado de eventos<\/h4>\n<p>\nPara validar mensajes en Kafka, se puede utilizar <noindex><a rel=\"nofollow\" href=\"https:\/\/docs.confluent.io\/current\/schema-registry\/docs\/index.html\">Avro<\/a><\/noindex>, pero hab\u00eda que tener eso en cuenta desde el principio y utilizar Confluent. En nuestro caso con el versionado, hay que ser cauteloso. No siempre ser\u00e1 posible volver a leer los mensajes del replication log, porque el modelo \"se fue\". En general, se construyen versiones de tal manera que el modelo sea retrocompatible: por ejemplo, hacer que un campo sea temporalmente no obligatorio. Si las diferencias son demasiado grandes, comenzamos a escribir en un nuevo t\u00f3pico, y trasladamos a los clientes cuando hayan terminado de leer el viejo.<\/p>\n<h4>Garant\u00eda de orden de lectura de las partitions<\/h4>\n<p>\nLos t\u00f3picos dentro de Kafka se dividen en partitions. Esto no es muy importante mientras dise\u00f1emos entidades e intercambios, pero es crucial cuando decidimos c\u00f3mo consumir y escalar.<\/p>\n<p>En circunstancias normales, escribes a un \u00fanico t\u00f3pico en Kafka. Por defecto, se utiliza una sola partici\u00f3n, y todos los mensajes de este t\u00f3pico caen en ella. El consumidor, a su vez, lee esos mensajes de forma secuencial. Supongamos que ahora es necesario expandir el sistema para que dos consumidores diferentes lean los mensajes. Si, por ejemplo, est\u00e1s enviando un SMS, puedes decirle a Kafka que haga una partici\u00f3n adicional, y Kafka comenzar\u00e1 a repartir los mensajes en dos partes: mitad aqu\u00ed, mitad all\u00ed. <\/p>\n<p>\u00bfC\u00f3mo los divide Kafka? Cada mensaje tiene un cuerpo (donde almacenamos el JSON) y tiene una clave. A esta clave se le puede aplicar una funci\u00f3n hash, que determinar\u00e1 en qu\u00e9 partici\u00f3n caer\u00e1 el mensaje.<\/p>\n<p>En nuestro caso con los reembolsos, esto es importante, ya que si tomamos dos particiones, existe la posibilidad de que un consumidor paralelo procese el segundo evento antes que el primero, lo que podr\u00eda causar problemas. La funci\u00f3n hash garantiza que los mensajes con la misma clave caer\u00e1n en la misma partici\u00f3n. <\/p>\n<h4>Eventos vs comandos<\/h4>\n<p>\nEste es otro problema con el que nos encontramos. Un evento es un cierto suceso: decimos que algo ocurri\u00f3 (something_happened), por ejemplo, que un art\u00edculo fue cancelado o que ocurri\u00f3 un reembolso. Si hay alguien escuchando estos eventos, cuando se produzca \"el art\u00edculo fue cancelado\", se crear\u00e1 la entidad de reembolso, y \"ocurri\u00f3 un reembolso\" se registrar\u00e1 en alguna parte de las configuraciones.<\/p>\n<p>Pero generalmente, cuando dise\u00f1as eventos, no quieres escribirlos en vano: esperas que alguien los lea. Existe una fuerte tentaci\u00f3n de no escribir something_happened (item_canceled, refund_refunded), sino algo como something_should_be_done. Por ejemplo, el art\u00edculo est\u00e1 listo para el retorno.<\/p>\n<p>Por un lado, esto sugiere c\u00f3mo se utilizar\u00e1 el evento. Por otro lado, se parece mucho menos a un nombre normal de evento. Adem\u00e1s, de aqu\u00ed ya no est\u00e1 lejos el comando do_something. Pero no tienes garant\u00eda de que este evento sea le\u00eddo por alguien; y si es le\u00eddo, no hay garant\u00eda de que se haya le\u00eddo correctamente; y si se ley\u00f3 correctamente, no hay seguridad de que se haya hecho algo, y que ese algo haya tenido \u00e9xito. En el momento en que el evento se convierte en do_something, se necesita retroalimentaci\u00f3n, y este es un problema.<\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/b755d91208092bd9791a41ce4633fb48.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nEn el intercambio asincr\u00f3nico en RabbitMQ, cuando lees un mensaje, vas a HTTP, y tienes una respuesta: al menos que el mensaje fue recibido. Cuando escribes en Kafka, existe un mensaje que indica que escribiste en Kafka, pero no sabes nada sobre c\u00f3mo fue procesado. <\/p>\n<p>Por lo tanto, en nuestro caso tuvimos que introducir un evento de respuesta y configurar la monitorizaci\u00f3n para que, si se produjeron cierta cantidad de eventos, dentro de un tiempo determinado deber\u00eda llegar la misma cantidad de eventos de respuesta. Si esto no sucedi\u00f3, parece que algo sali\u00f3 mal. Por ejemplo, si enviamos el evento \u00abitem_ready_to_refund\u00bb, esperamos que se genere un reembolso, que se devuelvan los fondos al cliente y que tengamos el evento \u00abmoney_refunded\u00bb. Pero esto no es seguro, por lo que se necesita monitorizaci\u00f3n.<\/p>\n<h3>Matices<\/h3>\n<p>\nHay un problema bastante obvio: si est\u00e1s leyendo de un t\u00f3pico de forma secuencial y tienes un mensaje que es malo, el consumidor falla y no puedes avanzar. Necesitas <strong>detener todos los consumidores<\/strong>, confirmar el offset para poder continuar leyendo.<\/p>\n<p>Sab\u00edamos de esto, nos preparamos para ello, y aun as\u00ed ocurri\u00f3. Esto sucedi\u00f3 porque el evento era v\u00e1lido desde la perspectiva del events-bus, el evento era v\u00e1lido desde la perspectiva del validador de la aplicaci\u00f3n, pero no era v\u00e1lido desde la perspectiva de PostgreSQL, porque en un sistema ten\u00edamos MySQL con UNSIGNED INT, y en el nuevo sistema solo hab\u00eda PostgreSQL con INT. Este \u00faltimo tiene un tama\u00f1o un poco menor y el Id no cab\u00eda. Symfony muri\u00f3 con una excepci\u00f3n. Por supuesto, capturamos la excepci\u00f3n, porque est\u00e1bamos preparados para ello, y est\u00e1bamos planeando confirmar este offset, pero antes quer\u00edamos incrementar el contador de problemas, dado que el mensaje se proces\u00f3 de manera fallida. Los contadores en este proyecto tambi\u00e9n se almacenan en la base de datos, y Symfony ya hab\u00eda cerrado la comunicaci\u00f3n con la base de datos, y otra excepci\u00f3n mat\u00f3 todo el proceso sin posibilidad de confirmar el offset.<\/p>\n<p>El servicio estuvo inactivo por un tiempo; afortunadamente, con Kafka no es tan grave, porque los mensajes permanecen. Cuando se reanude el trabajo, se podr\u00e1n leer. Esto es conveniente.<\/p>\n<p>Kafka tiene la posibilidad, a trav\u00e9s de herramientas, de establecer un offset arbitrario. Pero para hacerlo, es necesario detener todos los consumidores; en nuestro caso, preparar un lanzamiento separado en el que no haya consumidores ni reasignaciones. Entonces, a trav\u00e9s de herramientas, se puede mover el offset en Kafka y el mensaje pasar\u00e1.<\/p>\n<p>Otro matiz es <strong>replication log vs rdkafka.so<\/strong>\u00a0\u2014 est\u00e1 relacionado con la especificidad de nuestro proyecto. Usamos PHP, y en PHP, por lo general, todas las bibliotecas se comunican con Kafka a trav\u00e9s del repositorio rdkafka.so, y luego hay alg\u00fan tipo de envoltura. Puede que sean nuestras dificultades personales, pero result\u00f3 que simplemente releer un fragmento ya le\u00eddo no es tan sencillo. En general, tuvimos problemas de programaci\u00f3n.<\/p>\n<p>Volviendo a las caracter\u00edsticas del trabajo con partitions, est\u00e1 escrito directamente en la documentaci\u00f3n. <strong>consumers &gt;= topic partitions<\/strong>. Pero supe de esto mucho despu\u00e9s de lo que me gustar\u00eda. Si deseas escalar y tener dos consumidores, necesitas al menos dos partitions. Es decir, si ten\u00edas una partition en la que se acumularon 20,000 mensajes y creaste una fresca, el n\u00famero de mensajes no se equilibrar\u00e1 r\u00e1pidamente. Por lo tanto, para tener dos consumidores en paralelo, debes comprender c\u00f3mo funcionan las partitions.<\/p>\n<h2>Monitoreo<\/h2>\n<p>\nCreo que, seg\u00fan monitoreamos, ser\u00e1 a\u00fan m\u00e1s claro qu\u00e9 problemas hay en el enfoque actual.<\/p>\n<p>Por ejemplo, contamos cu\u00e1ntos productos en la base han cambiado recientemente su estado, y, por ende, a partir de estos cambios deber\u00edan haber ocurrido eventos, y enviamos este n\u00famero a nuestro sistema de monitoreo. Luego, de Kafka recibimos un segundo n\u00famero, cu\u00e1ntos eventos se registraron realmente. Obviamente, la diferencia entre estos dos n\u00fameros siempre deber\u00eda ser cero.<\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/07d8ed08514f2fb97d9019466b96342c.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAdem\u00e1s, es necesario monitorear c\u00f3mo est\u00e1 el productor, si el events-bus ha recibido los mensajes y c\u00f3mo est\u00e1 el consumidor. Por ejemplo, en los gr\u00e1ficos de abajo, todo est\u00e1 bien con el Refund Tool, pero claramente hay algunos problemas con BOB (picos azules).<\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/57112dbe70d2b388c53f19c34cca6f63.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nYa mencion\u00e9 el consumer-group lag. En t\u00e9rminos simples, es la cantidad de mensajes no le\u00eddos. En general, nuestros consumidores trabajan r\u00e1pido, as\u00ed que el lag suele ser 0, pero a veces puede haber un pico temporal. Kafka lo maneja de forma predeterminada, pero es necesario establecer un intervalo. <\/p>\n<p>Hay un proyecto <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/linkedin\/Burrow\">Burrow<\/a><\/noindex>, que te dar\u00e1 m\u00e1s informaci\u00f3n sobre Kafka. Simplemente a trav\u00e9s de la API, te proporciona el estado de cada grupo de consumidores respecto a c\u00f3mo va ese grupo. Adem\u00e1s de OK y Failed, hay advertencias, y podr\u00e1s saber si tus consumidores no est\u00e1n manejando el ritmo de producci\u00f3n: no logran procesar lo que se escribe. El sistema es bastante inteligente y es f\u00e1cil de usar. <\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/cece8495801e187b155487a802e1a35a.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nAs\u00ed se ve la respuesta de la API. Aqu\u00ed el grupo bob-live-fifa, partition refund.update.v1, estado OK, lag 0 \u2014 el \u00faltimo offset final es tal.<\/p>\n<p><img decoding=\"async\" alt=\"Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka.\" src=\"\/wp-content\/uploads\/2019\/04\/1538b2ccc390e9b83075f54566f1039b.png\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<br \/>\nMonitoreo <strong>updated_at SLA (atrapado)<\/strong> Ya mencion\u00e9 anteriormente. Por ejemplo, un producto ha pasado al estado de estar listo para la devoluci\u00f3n. Configuramos un Cron que indica que si en 5 minutos este objeto no ha pasado a refund (devolvemos el dinero a trav\u00e9s de los sistemas de pago muy r\u00e1pido), entonces algo definitivamente ha salido mal y es un caso para soporte. As\u00ed que simplemente utilizamos un Cron que lee tales situaciones, y si son m\u00e1s de 0, env\u00eda una alerta.<\/p>\n<p><b>En resumen, es conveniente utilizar eventos cuando<\/b>:<\/p>\n<ul>\n<li>la informaci\u00f3n es necesaria para varios sistemas;<\/li>\n<li>el resultado del procesamiento no es importante;<\/li>\n<li>hay pocos eventos o son eventos peque\u00f1os. <\/li>\n<\/ul>\n<blockquote><p>A primera vista, el art\u00edculo tiene un tema muy espec\u00edfico: API as\u00edncrono en Kafka, pero sobre \u00e9l me gustar\u00eda recomendar muchas cosas desde el principio.<br \/>\nEn primer lugar, el siguiente <noindex><a rel=\"nofollow\" href=\"https:\/\/www.highload.ru\/\">HighLoad++.<\/a><\/noindex> no hay que esperar hasta noviembre, habr\u00e1 una versi\u00f3n en San Petersburgo en abril y en junio hablaremos sobre cargas altas en Novosibirsk.<br \/>\nEn segundo lugar, el autor del informe, Sergey Zaika, es miembro del Comit\u00e9 Program\u00e1tico de nuestra nueva conferencia sobre gesti\u00f3n del conocimiento. <noindex><a rel=\"nofollow\" href=\"https:\/\/knowledgeconf.ru\/2019\">KnowledgeConf<\/a><\/noindex>La conferencia es de un d\u00eda, tendr\u00e1 lugar el 26 de abril, y su programa es muy completo.<br \/>\nAdem\u00e1s, en mayo habr\u00e1 <noindex><a rel=\"nofollow\" href=\"https:\/\/phprussia.ru\/2019\">PHP Russia<\/a><\/noindex> y\u00a0<noindex><a rel=\"nofollow\" href=\"https:\/\/ritfest.ru\/2019\">RIT++<\/a><\/noindex> (con DevOpsConf en el programa) \u2014 todav\u00eda se pueden presentar propuestas, compartir experiencias y quejarse de sus errores cometidos.<\/p><\/blockquote>\n<p>Fuente: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/oleg-bunin\/blog\/445424\/\">habr.com<\/a><\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441\u00a0\u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438\u00a0\u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434? \u041c\u043e\u0442\u0438\u0432\u0430\u0446\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0441\u043e\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e \u0440\u0430\u0437\u043d\u0430\u044f: \u043e\u0442\u00a0\u0437\u0430\u043a\u043e\u043d\u043e\u0434\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0434\u043e\u00a0\u043f\u0440\u0438\u0441\u0443\u0449\u0435\u0433\u043e \u0432\u0441\u0435\u043c \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0441\u0442\u0430\u043c \u0436\u0435\u043b\u0430\u043d\u0438\u044f \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c. \u041d\u043e\u00a0\u044d\u0442\u043e \u0432\u043e\u0432\u0441\u0435 \u043d\u0435\u00a0\u0437\u043d\u0430\u0447\u0438\u0442, \u0447\u0442\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u0440\u0430\u0441\u0441\u0447\u0438\u0442\u044b\u0432\u0430\u0442\u044c \u043d\u0430\u00a0\u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u0443\u044e \u0432\u044b\u0433\u043e\u0434\u0443. \u0412\u00a0\u0447\u0435\u043c \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e \u043c\u043e\u0436\u043d\u043e \u0432\u044b\u0438\u0433\u0440\u0430\u0442\u044c, \u0435\u0441\u043b\u0438 \u0432\u043d\u0435\u0434\u0440\u0438\u0442\u044c events-driven API \u043d\u0430\u00a0Kafka, \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0435\u0442 \u0421\u0435\u0440\u0433\u0435\u0439 \u0417\u0430\u0438\u043a\u0430 (fewald). \u041f\u0440\u043e \u043d\u0430\u0431\u0438\u0442\u044b\u0435 \u0448\u0438\u0448\u043a\u0438 \u0438\u00a0\u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u044b\u0435 \u043e\u0442\u043a\u0440\u044b\u0442\u0438\u044f \u0442\u043e\u0436\u0435 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":22763,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-30778","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.2 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"es_ES\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u041e\u043f\u044b\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 Refund Tool \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u043c API \u043d\u0430 Kafka | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2019-10-31T18:37:23+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2019-10-31T18:37:23+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Experiencia en el desarrollo del servicio Refund Tool con API as\u00edncrono en Kafka | ProHoster","description":"\u00bfQu\u00e9 podr\u00eda hacer que una gran empresa como Lamoda, con procesos establecidos y decenas de servicios interconectados, cambie significativamente de enfoque?","canonical_url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"es_ES","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u041e\u043f\u044b\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u0430 Refund Tool \u0441 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u043c API \u043d\u0430 Kafka | ProHoster","og:description":"\u0427\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u0441\u0442\u0430\u0432\u0438\u0442\u044c \u0442\u0430\u043a\u0443\u044e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u044e \u043a\u0430\u043a Lamoda \u0441 \u043e\u0442\u043b\u0430\u0436\u0435\u043d\u043d\u044b\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u043c \u0438 \u0434\u0435\u0441\u044f\u0442\u043a\u0430\u043c\u0438 \u0432\u0437\u0430\u0438\u043c\u043e\u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043c\u0435\u043d\u044f\u0442\u044c \u043f\u043e\u0434\u0445\u043e\u0434?","og:url":"https:\/\/prohoster.info\/es\/blog\/administrirovanie\/opyt-razrabotki-servisa-refund-tool-s-asinhronnym-api-na-kafka","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2019-10-31T18:37:23+00:00","article:modified_time":"2019-10-31T18:37:23+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"30778","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":"2026-01-21 02:58:19","breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-03-01 03:29:56","updated":"2026-01-21 02:58:19","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/30778","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/comments?post=30778"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/posts\/30778\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media\/22763"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/media?parent=30778"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/categories?post=30778"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/es\/wp-json\/wp\/v2\/tags?post=30778"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}