Microservice Decorator

coordinare i microservizi in modo semplice e dichiarativo

Tommaso Allevi

@allevo

Platform R&D Leader @ Mia-Platform

Perchè sviluppare a microservizi?

Disaccoppia componenti del progetto

Ogni microservizio può essere sviluppato con un suo linguaggio

Aiuta a dividere il problema in sottoproblemi

Scala i tuoi microservizi in modo indipendente

Riutilizzare i microservizi

è possibile testare la singola business logic

Sviluppo incrementale di feature

Riutilizzare i microservizi

Sviluppo incrementale di feature

Dipendenze forti tra microservizi

Open/closed principle

Un'entità software dovrebbe essere aperta alle estensioni, ma chiusa alle modifiche.

Wikipedia

Esiste un modo per estendere senza modificare?

Microservice Decorator

Business Logic
API

Decoratore pre1

Decoratore pre2

Decoratore pre3

...

Decoratore post1

Decoratore post2

Decoratore post3

...

Flow

Un decoratore è
un componente dell'architettura
che può:

  • Ricevere una notifica di quando c'è una richiesta/risposta
  • Modificare la richiesta/risposta aggiungendo/togliendo parametri (querystring, header, body...)
  • Fermare la richiesta stabilendo la risposta definitiva da dare al client (per esempio un check andato male)

Una possibile implementazione può essere fatta usando il protocollo HTTP nel quale il decoratore accetta in input la richiesta/risposta e risponde in base all'azione che deve essere compiuta

  GET /incoming_url?foo=bar

pre1

pre2

business-service

  GET /proxied-url?bar=foo

post3

{
  "routes": {
    "/incoming_url": {
      "GET": {
        "pre": [ "pre1", "pre2" ],
        "call": "http://business-service/proxied-url",
        "post": [ "post1" ]
      }
    },
  },
  "decorators": {
    "pre1": {
      "call": "http://some-service2/decorators/path1",
    },
    "pre2": {
      "call": "http://other-service/decorators/path2",
    },
    "post1": {
      "call": "http://some-service3/decorators/path3",
    }
  }
}

Il Microservice Decorator impone un interfaccia HTTP che ogni decoratore deve implementare

Un microservizio NON sono le API che espone

 ma la business logic che implementa

È una limitazione?

No

Ports and Adapter Pattern

Esempio #1

Login

auth-service

session-manager

{
  "routes": {
    "/login": {
      "POST": {
        "pre": [ ],
        "call": "http://auth-service/identify",
        "post": [ "createSession" ]
      }
    }
  },
  "decorators": {
    "createSession": {
      "call": "http://session-manager/decorators/createSession",
    }
  }
}

Microservice Decorator

Auth
Service

Session

Manager

POST /login

Microservice
Decorator

POST /identify

POST
/decorators/createSession

POST /login

{ username: "aa", password: "bb" }

---> { userId: "1234" }

POST /decorators/createSession

{ userId: "1234" }

---> Set-Cookie: sid="abcd"

---> { userId: "1234" }

POST /identify

{ username: "aa", password: "bb" }

---> Set-Cookie: sid="abcd"

---> { userId: "1234" }

Esempio #2

Login con CSRF

Microservice Decorator

Auth

Service

Session

Manager

Microservice
Decorator

CSRF

Service

1

2

3

{
  "routes": {
    "/login": {
      "POST": {
        "pre": [ "checkCSRF" ],
        "call": "http://auth-service/identify",
        "post": [ "createSession" ]
      }
    }
  },
  "decorators": {
    "createSession": {
      "call": "http://session-manager/decorators/createSession",
    },
    "checkCSRF": {
      "call": "http://csrf-service/decorators/checkCSRF",
    }
  }
}

Nessuna linea di codice esistente è stata modificata!

Abbiamo aggiunto le linee di codice necessarie per la nuova feature in un microservizio separato e modificato un file di configurazione

Esempio #3

Registrazione

{
  "/signup": {
    "POST": {
      "pre": [ ],
      "call": "http://auth-service/signup",
      "post": [ ]
    }
  },
  "decorators": {
  }
}

Se volessimo aggiungere il controllo per il CSRF token?

{
  "routes": {
    "/signup": {
      "POST": {
        "pre": [ "checkCSRF" ],
        "call": "http://auth-service/signup",
        "post": [ ]
      }
    }
  },
  "decorators": {
    "createSession": {
      "call": "http://session-manager/decorators/createSession",
    }
  }
}

Se volessimo che l'utente sia già

loggato dopo la registrazione?

{
  "routes": {
    "/signup": {
      "POST": {
        "pre": [ "checkCSRF" ],
        "call": "http://auth-service/signup",
        "post": [ "createSession" ]
      }
    }
  },
  "decorators": {
    "createSession": {
      "call": "http://session-manager/decorators/createSession",
    },
    "checkCSRF": {
      "call": "http://csrf-service/decorators/checkCSRF",
    }
  }
}

E la mail di benvenuto?

{
  "routes": {
    "/signup": {
      "POST": {
        "pre": [ "checkCSRF" ],
        "call": "http://auth-service/signup",
        "post": [ "createSession", "sendWelcomeMail" ]
      }
    }
  },
  "decorators": {
    ...
    "sendWelcomeMail": {
      "call": "http://mail-service/decorators/welcome"
    }
  }
}

Aggiungi comportamenti senza modificare i tuoi microservizi

  • Tieni la business logic separata da side effects
  • Aggiungi incrementalmente feature senza modificare il codice esistente
  • Descrivi la tua architettura attraverso un unico file di configurazione

Esempio #4

Integrazione con un servizio Legacy

Requisiti

  1. la login Legacy deve continuare a funzionare senza interruzioni
  2. i nuovi servizi sono microservizi e il loro metodo di autenticazione è diverso da quello Legacy
  3. i nuovi servizi non accedono al database delle sessioni Legacy
  4. tra i nuovi servizi c'è un authentication service e un session manager
  5. i clients hanno una gestione dei cookie coerente (come un browser)
  6. tutti i servizi (nuovi e Legacy) sono servizi HTTP

Supponiamo di avere un "bel" monolita che per anni ha fatto il proprio lavoro bene e ora vorremmo accostare nuovi servizi a contorno che hanno un metodo di autenticazione diverso

Authentication Service

Session Manager

Credenziali => userId

userId => sessione (cookie, jwt...)

sessione (cookie, jwt...) => userId

Legacy service

POST /login: Credenziali => userId

GET /me: cookie => userId

Legacy

POST /login

SM

POST /login

{ ... }

+

sid cookie

GET /me

{ userId }

post decorator

{ ... }

+

sid cookie

+

sid2 cookie

{
  "routes": {
    "/login": {
      "POST": {
        "pre": [ ],
        "call": "http://legacy/login",
        "post": [ "createNewSession" ]
      }
    }
  },
  "decorators": {
    "createNewSession": { "call": "http://session-manager/decorators/createSession" }
  }
}

Possiamo aggiungere altri decoratori per ampliare le features implementate

CSRF token!

Route /login - Method POST

                   +-------------------+                                   +-----------------------+                    
                   |                   |                                   |                       |                    
 request ---------->     checkCSRF     +--------->     Legacy    ---------->    createNewSession   +---------> response 
                   |                   |                                   |                       |                    
                   +---------^---------+                                   +-----------^-----------+                    
                             |                                                         |                                
                             |                                                         |                                
                             v                                                         v                                
                       csrf-service                                             session-manager                         
                   /decorators/checkCSRF                                 /decorators/createNewSession                    

E la documentazione?

La configurazione del Microservice Decorator è già documentazione!

E per gli amanti dell'ASCII art:

{
  "routes": {
    "/orders/": {
      "POST": {
        "pre": [ "checkDishesAvailability", "checkAddress" ],
        "call": "http://order-service/orders/",
        "post": [ "makePayment", "markTheOrderAsPaid", "notifyTheUser", "notifyTheDeliveryBoy" ]
      }
    }
  },
  "decorators": {
    "checkDishesAvailability": { "call": "http://dish-service/decorators/checkAvailability" },
    "checkAddress": { "call": "http://address-service/decorators/checkAddress" },
    "makePayment": { "call": "http://payment-service/decorators/payForTheOrder" },
    "markTheOrderAsPaid": { "call": "http://order-service/decorators/markAsPaid" },
    "notifyTheUser": { "call": "http://email-service/decorators/sendOrderMail" },
    "notifyTheDeliveryBoy": { "call": "http://push-service/decorators/sendPushToDeliveryBoy" }
  }
}

è un flusso di un ordine!

Quale flusso implementa questa configurazione?

Grazie!

(we are hiring!)

tommaso.allevi@mia-platform.eu

Microservice Decorator - Crafted Software

By Tommaso Allevi

Microservice Decorator - Crafted Software

  • 313