Social Bot Scheduler

📐 Arquitectura — Caso 19: #️⃣ F# (.NET) → 🌉 n8n → 🍀 Clojure (Ring) + XTDB#

Status: Pendiente Origen: F# Destino: Clojure Persistencia: XTDB Puente: n8n

Emisor F# (funcional-first sobre .NET) que reenvía a n8n; el receptor Clojure/Ring persiste en XTDB, una BD bitemporal e inmutable embebida in-process. Dos runtimes funcionales, cero contenedor de BD.


🧭 Ficha técnica#

AtributoValor
ID19
OrigenF# / .NET 9 — origin/Publisher.fs
Puenten8n — case-19-fsharp-to-clojure.json
DestinoClojure / Ring — dest/src/receiver/core.clj
PersistenciaXTDB 1.24 (bitemporal, embebida in-memory)
Puerto (dashboard)http://localhost:8099
Perfil Dockercase19

🗺️ Diagrama de arquitectura#

diagrama mermaid
%%{init: {'theme':'base','themeVariables':{'fontSize':'26px','fontFamily':'Segoe UI, Arial, sans-serif'},'flowchart':{'useMaxWidth':true,'htmlLabels':false,'nodeSpacing':55,'rankSpacing':70,'padding':16,'diagramPadding':16}}%%
flowchart TB
    subgraph ORIGIN["ORIGEN - F# (.NET)"]
        F["Publisher.fs"]
    end

    subgraph BRIDGE["PUENTE - n8n + Guardrails"]
        C(["Webhook"]) --> IDEM{"Idempotencia + circuit breaker"}
        IDEM -->|nuevo| FWD["HTTP forward + reintentos"]
        FWD -.->|error| DLQ["Dead Letter Queue (/errors)"]
    end

    subgraph DEST["DESTINO - Clojure/Ring (JVM)"]
        FWD --> CLJ["core.clj (Ring)"]
        CLJ -->|put / q Datalog| DB[("XTDB embebida (bitemporal)")]
        DB --> DASH["Dashboard :8099"]
    end

    F -->|POST JSON| C

    classDef origin fill:#378BBA,stroke:#333,color:#fff
    classDef bridge fill:#EA4B71,stroke:#333,color:#fff
    classDef dest fill:#5881D8,stroke:#333,color:#fff
    classDef db fill:#63B132,stroke:#333,color:#000
    class F origin
    class C,IDEM,FWD,DLQ bridge
    class CLJ,DASH dest
    class DB db

🔁 Diagrama de secuencia (ciclo de una publicación)#

diagrama mermaid
%%{init: {'theme':'base','themeVariables':{'fontSize':'24px','fontFamily':'Segoe UI, Arial, sans-serif'},'sequence':{'useMaxWidth':true,'actorFontSize':22,'messageFontSize':20,'noteFontSize':18,'actorMargin':90,'boxMargin':16,'width':180}}}%%
sequenceDiagram
    autonumber
    participant FS as Publisher.fs (F#)
    participant N8N as n8n
    participant CLJ as Clojure/Ring
    participant XT as XTDB

    FS->>N8N: POST /webhook (post vencido)
    N8N->>N8N: Circuit breaker + idempotencia
    N8N->>CLJ: HTTP POST /webhook (reintentos x3)
    CLJ->>XT: submit-tx [::xt/put doc] + await-tx
    XT-->>CLJ: tx confirmada
    CLJ-->>N8N: 200 OK
    Note over CLJ,XT: GET /logs -> xt/q Datalog -> ordena por created-at desc, top 20

🧩 Componentes#

#️⃣ Origen — F# (.NET)#

  • origin/Publisher.fs reenvía los posts vencidos a n8n con HttpClient. Estilo funcional (inmutable, expresiones).

🌉 Puente — n8n#

  • Guardrails canónicos: fingerprint → circuit breaker → idempotencia → HTTP forward con reintentos → DLQ.

🍀 Destino — Clojure/Ring + XTDB#

  • dest/src/receiver/core.clj levanta Ring/Jetty y un nodo XTDB in-memory. /webhook hace submit-tx + await-tx; /logs consulta con Datalog. Empaquetado como uberjar (Leiningen).

▶️ Cómo levantarlo#

bash
docker-compose --profile case19 up -d          # receptor Clojure + XTDB embebida

Dashboard: http://localhost:8099


🔗 Enlaces#


Diagramas en Mermaid — se renderizan nativamente en GitHub. Parte de Social Bot Scheduler.