{"id":50968858,"url":"https://github.com/raulrobinson/idempotency-lab-opensource","last_synced_at":"2026-06-18T23:32:34.399Z","repository":{"id":363168657,"uuid":"1262126503","full_name":"raulrobinson/idempotency-lab-opensource","owner":"raulrobinson","description":"Laboratorio basico que permite demostrar y comprobar el patron de idempotencia en las operaciones","archived":false,"fork":false,"pushed_at":"2026-06-07T17:35:46.000Z","size":73,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-07T19:19:32.355Z","etag":null,"topics":["idempotency","java","laboratory-exercises","mongodb","nodejs","opensource","reactive","webflux"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/raulrobinson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-07T15:56:13.000Z","updated_at":"2026-06-07T17:35:49.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/raulrobinson/idempotency-lab-opensource","commit_stats":null,"previous_names":["raulrobinson/idempotency-lab-opensource"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/raulrobinson/idempotency-lab-opensource","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raulrobinson%2Fidempotency-lab-opensource","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raulrobinson%2Fidempotency-lab-opensource/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raulrobinson%2Fidempotency-lab-opensource/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raulrobinson%2Fidempotency-lab-opensource/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/raulrobinson","download_url":"https://codeload.github.com/raulrobinson/idempotency-lab-opensource/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raulrobinson%2Fidempotency-lab-opensource/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34511617,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["idempotency","java","laboratory-exercises","mongodb","nodejs","opensource","reactive","webflux"],"created_at":"2026-06-18T23:32:34.125Z","updated_at":"2026-06-18T23:32:34.386Z","avatar_url":"https://github.com/raulrobinson.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Laboratorio Basico de Idempotencia\n\n![Java](https://img.shields.io/badge/Java-21-orange?style=for-the-badge\u0026logo=openjdk)\n![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-brightgreen?style=for-the-badge\u0026logo=springboot)\n![Spring WebFlux](https://img.shields.io/badge/Spring%20WebFlux-Reactive-6DB33F?style=for-the-badge\u0026logo=spring)\n![Spring Cloud Gateway](https://img.shields.io/badge/Spring%20Cloud-Gateway-6DB33F?style=for-the-badge\u0026logo=spring)\n![Node.js](https://img.shields.io/badge/Node.js-22-339933?style=for-the-badge\u0026logo=nodedotjs)\n![MongoDB](https://img.shields.io/badge/MongoDB-7-47A248?style=for-the-badge\u0026logo=mongodb)\n![Docker](https://img.shields.io/badge/Docker-Compose-2496ED?style=for-the-badge\u0026logo=docker)\n![Idempotency](https://img.shields.io/badge/Pattern-Idempotency-blueviolet?style=for-the-badge)\n![Reactive](https://img.shields.io/badge/Architecture-Reactive-blue?style=for-the-badge)\n![License](https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge)\n\n### Introducción:\nEste laboratorio implementa un patrón de idempotencia independiente del dominio. \n1. El cliente envía una petición HTTP con un Idempotency-Key; \n2. API Gateway la enruta hacia un servicio WebFlux encargado de controlar si la operación debe ejecutarse, bloquearse o responderse desde una respuesta previamente almacenada.\n3. El servicio de idempotencia válida la clave, calcula un hash del payload y consulta MongoDB para identificar el estado de la petición. \n4. Si la clave no existe, registra la operación como PROCESSING, invoca el servicio de dominio implementado en Node.js y guarda la respuesta final como COMPLETED. \n5. Si la misma petición se repite con la misma clave y el mismo payload, el sistema no vuelve a ejecutar el dominio, sino que retorna la respuesta cacheada.\n6. Si la clave se reutiliza con un payload diferente, el sistema responde con error 422. \n7. Si otra petición con la misma clave aún está en proceso, responde con 409. \n8. De esta forma, el componente evita duplicidad de operaciones, mantiene consistencia y puede reutilizarse para distintos casos de negocio sin depender de la lógica interna del dominio.\n\n---\n\n- Diagrama General:\n\n```\n         Cliente\n            ↓\n    Spring Cloud Gateway\n            ↓\nServicio de Idempotencia\n    ↓               ↓\n MongoDB      Lambda Node.js\n```\n\n---\n\n- Diagrama de Flujo:\n```mermaid\nflowchart LR\n    CLIENT[Cliente]\n\n    GATEWAY[Spring Cloud Gateway\u003cbr/\u003eAPI Gateway]\n\n    IDEM[Spring WebFlux\u003cbr/\u003eIdempotency Service]\n\n    MONGO[(MongoDB\u003cbr/\u003eIdempotency Store)]\n\n    DOMAIN[Lambda Node.js\u003cbr/\u003eServicio de Dominio]\n\n    CLIENT --\u003e GATEWAY\n    GATEWAY --\u003e IDEM\n\n    IDEM --\u003e|consulta / guarda estado| MONGO\n    IDEM --\u003e|solo si es nueva petición| DOMAIN\n\n    DOMAIN --\u003e IDEM\n    IDEM --\u003e GATEWAY\n    GATEWAY --\u003e CLIENT\n``` \n\n---\n\n- Diagrama de Secuencia:\n\n```mermaid\nsequenceDiagram\n    autonumber\n\n    actor Client as Cliente / Postman\n    participant GW as Spring Cloud Gateway\u003cbr/\u003e:8080\n    participant API as Idempotency Controller\u003cbr/\u003eSpring WebFlux :8081\n    participant IDEM as Idempotency Service\n    participant MONGO as MongoDB\u003cbr/\u003eidempotency_records\n    participant NODE as Lambda Node.js\u003cbr/\u003eDomain Handler :3001\n\n    Client-\u003e\u003eGW: POST /api/execute\u003cbr/\u003eIdempotency-Key: order-001\u003cbr/\u003eJSON Payload\n    GW-\u003e\u003eGW: RewritePath\u003cbr/\u003e/api/execute -\u003e /idempotency/execute\n    GW-\u003e\u003eAPI: POST /idempotency/execute\u003cbr/\u003eIdempotency-Key + Payload\n\n    API-\u003e\u003eIDEM: handle(idempotencyKey, payload)\n\n    alt Idempotency-Key ausente o vacía\n        IDEM--\u003e\u003eAPI: 400 Bad Request\u003cbr/\u003eIdempotency-Key header is required\n        API--\u003e\u003eGW: 400\n        GW--\u003e\u003eClient: 400 Bad Request\n    else Idempotency-Key presente\n        IDEM-\u003e\u003eIDEM: Generate SHA-256 requestHash(payload)\n        IDEM-\u003e\u003eMONGO: findById(idempotencyKey)\n\n        alt No existe registro\n            MONGO--\u003e\u003eIDEM: empty\n\n            IDEM-\u003e\u003eMONGO: insert PROCESSING\u003cbr/\u003e_id = idempotencyKey\u003cbr/\u003erequestHash\u003cbr/\u003estatus = PROCESSING\u003cbr/\u003eexpiresAt\n\n            alt Insert exitoso / lock adquirido\n                MONGO--\u003e\u003eIDEM: PROCESSING saved\n\n                IDEM-\u003e\u003eNODE: POST /invoke\u003cbr/\u003epayload original\n\n                alt Dominio responde OK\n                    NODE--\u003e\u003eIDEM: 200 OK\u003cbr/\u003edomainResponse\n\n                    IDEM-\u003e\u003eMONGO: update status COMPLETED\u003cbr/\u003eresponseStatus = 200\u003cbr/\u003eresponseBody = domainResponse\n                    MONGO--\u003e\u003eIDEM: COMPLETED saved\n\n                    IDEM--\u003e\u003eAPI: 200 OK\u003cbr/\u003edomainResponse\n                    API--\u003e\u003eGW: 200 OK\n                    GW--\u003e\u003eClient: 200 OK\u003cbr/\u003erespuesta original\n                else Dominio responde error\n                    NODE--\u003e\u003eIDEM: 500 Error\n\n                    IDEM-\u003e\u003eMONGO: update status FAILED\u003cbr/\u003eerrorMessage\n                    MONGO--\u003e\u003eIDEM: FAILED saved\n\n                    IDEM--\u003e\u003eAPI: 500 Internal Server Error\u003cbr/\u003eDomain execution failed\n                    API--\u003e\u003eGW: 500\n                    GW--\u003e\u003eClient: 500 Internal Server Error\n                end\n\n            else DuplicateKeyException / otro request ganó el lock\n                MONGO--\u003e\u003eIDEM: duplicate key\n                IDEM--\u003e\u003eAPI: 409 Conflict\u003cbr/\u003eRequest is already processing\n                API--\u003e\u003eGW: 409\n                GW--\u003e\u003eClient: 409 Conflict\n            end\n\n        else Existe registro\n            MONGO--\u003e\u003eIDEM: existing record\n\n            IDEM-\u003e\u003eIDEM: compare existing.requestHash\u003cbr/\u003evs current requestHash\n\n            alt Misma key pero payload diferente\n                IDEM--\u003e\u003eAPI: 422 Unprocessable Entity\u003cbr/\u003eIdempotency-Key reused with different payload\n                API--\u003e\u003eGW: 422\n                GW--\u003e\u003eClient: 422 Unprocessable Entity\n\n            else Payload igual y status COMPLETED\n                IDEM--\u003e\u003eAPI: cached response\u003cbr/\u003estatus + responseBody\n                API--\u003e\u003eGW: respuesta cacheada\n                GW--\u003e\u003eClient: misma respuesta original\n\n            else Payload igual y status PROCESSING\n                IDEM--\u003e\u003eAPI: 409 Conflict\u003cbr/\u003eRequest is already processing\n                API--\u003e\u003eGW: 409\n                GW--\u003e\u003eClient: 409 Conflict\n\n            else Payload igual y status FAILED\n                IDEM--\u003e\u003eAPI: 500 Internal Server Error\u003cbr/\u003ePrevious request failed\n                API--\u003e\u003eGW: 500\n                GW--\u003e\u003eClient: 500 Internal Server Error\n            end\n        end\n    end\n```\n\n---\n\n- Diagrama de Componentes:\n\n```mermaid\nflowchart TB\n    CLIENT[Cliente / Postman]\n\n    subgraph LAB[Idempotency Lab Local]\n\n        subgraph GATEWAY[Gateway Service\u003cbr/\u003eSpring Cloud Gateway :8080]\n            ROUTES[Route Locator\u003cbr/\u003e/api/**]\n            REWRITE[RewritePath Filter\u003cbr/\u003e/api/execute -\u003e /idempotency/execute]\n            PROXY[Reactive Proxy]\n        end\n\n        subgraph IDEM[Idempotency Service\u003cbr/\u003eSpring Boot WebFlux :8081]\n            CONTROLLER[IdempotencyController\u003cbr/\u003ePOST /idempotency/execute]\n            SERVICE[IdempotencyService\u003cbr/\u003eOrquestador]\n            HASHER[Request Hasher\u003cbr/\u003eSHA-256 payload]\n            POLICY[Policy Handler\u003cbr/\u003eTTL / estados / errores]\n            WEBCLIENT[WebClient\u003cbr/\u003eHTTP client reactivo]\n            REPOSITORY[Reactive Mongo Repository]\n        end\n\n        subgraph DOMAIN[Lambda Node Domain\u003cbr/\u003eNode.js :3001]\n            EXPRESS[Express Adapter\u003cbr/\u003ePOST /invoke]\n            HANDLER[Lambda Handler\u003cbr/\u003ebusiness simulation]\n            ERROR_SIM[Forced Error Simulator\u003cbr/\u003eforceError=true]\n        end\n\n        subgraph DATA[MongoDB :27017]\n            IDEM_COLLECTION[(idempotency_records)]\n            DLQ_COLLECTION[(failed_events opcional)]\n        end\n    end\n\n    CLIENT --\u003e|POST /api/execute\u003cbr/\u003eIdempotency-Key + JSON| ROUTES\n    ROUTES --\u003e REWRITE\n    REWRITE --\u003e PROXY\n    PROXY --\u003e CONTROLLER\n\n    CONTROLLER --\u003e SERVICE\n    SERVICE --\u003e HASHER\n    SERVICE --\u003e POLICY\n    SERVICE --\u003e REPOSITORY\n\n    REPOSITORY --\u003e|findById / save / update| IDEM_COLLECTION\n\n    SERVICE --\u003e|solo si request nuevo| WEBCLIENT\n    WEBCLIENT --\u003e EXPRESS\n    EXPRESS --\u003e HANDLER\n    HANDLER --\u003e ERROR_SIM\n\n    HANDLER --\u003e|domain response| EXPRESS\n    EXPRESS --\u003e WEBCLIENT\n    WEBCLIENT --\u003e SERVICE\n\n    SERVICE --\u003e|save COMPLETED / FAILED| REPOSITORY\n    SERVICE --\u003e|response cacheada o nueva| CONTROLLER\n    CONTROLLER --\u003e PROXY\n    PROXY --\u003e CLIENT\n\n    POLICY -.errores controlados.-\u003e DLQ_COLLECTION\n```\n\n---\n\n### Licencia:\nEste proyecto está bajo la Licencia MIT. Consulta el archivo LICENSE para más detalles.\n\n### Autor:\n- [Raul R. Bolivar Navas](https://github.com/raulrobinson/idempotency-lab-opensource)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraulrobinson%2Fidempotency-lab-opensource","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fraulrobinson%2Fidempotency-lab-opensource","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraulrobinson%2Fidempotency-lab-opensource/lists"}