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
Implementazione in un monolite
Auth
Service
Session
Manager
HTTP handler
Implementazione in un monolita
Risoluzione delle credenziali
Gestione della session HTTP
MVC, MVVM, etc...
app.post('/login', async function (request) {
const { username, password } = request.body
const user = await authService.findUserByUsernameAndPassword(username, password)
if (!user) throw new httpError.BadRequest()
sessionManager.loginUser(request, user)
return user
})
auth-service
session-manager
Dipendenza tra microservizi
Auth
Service
Session
Manager
Dipendenza tra microservizi
1
Abbiamo due microservizi!
Dipendenze forti tra microservizi
Aggregatore / Backend For Frontend
Aggregatore / Backend For Frontend
Auth
Service
Session
Manager
Aggregatore
1
2
Abbiamo tre microservizi
e un pattern architetturale
Cosa mai potrà andare storto?
Se dovessimo introdurre una nuova feature, quali componenti dovremmo modificare?
Dipendenza tra microservizi
Aggregatore / Backend for frontend
È una buona pratica?
Open/closed principle
Un'entità software dovrebbe essere aperta alle estensioni, ma chiusa alle modifiche.
Wikipedia
Esiste un modo per estendere senza modificare?
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
Business Logic
API
Decoratore pre1
Decoratore pre2
Decoratore pre3
...
Decoratore post1
Decoratore post2
Decoratore post3
...
Flow
{
"/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",
}
}
}
GET /incoming_url?foo=bar
pre1
pre2
business-service
GET /proxied-url?bar=foo
post3
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
Microservice Decorator
{
"/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" }
Dipendenza tra microservizi
Cross-site request forgery token
stringa random valida una volta sola che evita
di eseguire due volte la stessa richiesta
Auth
Service
Session
Manager
Dipendenza tra microservizi
CSRF
Service
1
2
Aggregatore / Backend For Frontend
Aggregatore / Backend For Frontend
Auth
Service
Session
Manager
Aggregatore
2
3
CSRF
Service
1
È proprio necessario modificare il codice esistente per aggiungere un controllo?
Entrambe le soluzioni prevedono la modifica di un codice già esistente
Microservice Decorator
Microservice Decorator
Auth
Service
Session
Manager
Microservice
Decorator
CSRF
Service
1
2
3
{
"/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
Microservice Decorator
{
"/signup": {
"POST": {
"pre": [ ],
"call": "http://auth-service/signup",
"post": [ ]
}
},
"decorators": {
}
}
Se volessimo aggiungere il controllo per il CSRF token?
{
"/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?
{
"/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?
{
"/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
Microservice Decorator
Requisiti
Simuliamo ora un sito di food delivery presso cui l'utente può selezionare i piatti che vorrebbe ordinare
order-service
dish-service
payment-service
address-service
email-service
push-service
{
"/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" }
}
}
Route /login - Method POST
+-------------------+ +-----------------------+
| | | |
request ----------> checkCSRF +---------> PROXY Request ----------> createSession +---------> response
| | | |
+---------^---------+ +-----------^-----------+
| |
| |
v v
csrf-service session-manager
/decorators/checkCSRF /decorators/createSession
Route /signup - Method POST
+-------------------+ +-----------------------+
| | | |
request ----------> checkCSRF +---------> PROXY Request ----------> createSession +---------> response
| | | |
+---------^---------+ +-----------^-----------+
| |
| |
v v
csrf-service session-manager
/decorators/checkCSRF /decorators/createSession
Route /orders/ - Method POST
+---------------------------+ +----------------------+ +------------------------+ +--------------------+ +-----------------------+ +-------------------------------+
| | | | | | | | | | | |
request ----------> checkDishesAvailability +---------> checkAddress +---------> PROXY Request ----------> makePayment +---------> markTheOrderAsPaid +---------> notifyTheUser +---------> notifyTheDeliveryBoy +---------> response
| | | | | | | | | | | |
+-------------^-------------+ +-----------^----------+ +------------^-----------+ +----------^---------+ +-----------^-----------+ +---------------^---------------+
| | | | | |
| | | | | |
v v v v v v
dish-service address-service payment-service order-service email-service push-service
/decorators/checkAvailability /decorators/checkAddress /decorators/payForTheOrder /decorators/markAsPaid /decorators/sendOrderMail /decorators/sendPushToDeliveryBoy
E la documentazione?
La configurazione del Microservice Decorator è già documentazione!
E per gli amanti dell'ASCII art:
(we are hiring!)