Hlavní navigace

Mikroslužby založené na REST API

Pavel Tišnovský

V úvodních částech seriálu jsme se zabývali teoretickým popisem technologií, které jsou při tvorbě a provozu mikroslužeb používány. Pochopitelně nás však budou zajímat i praktické příklady, jimiž se začneme zabývat dnes.

Doba čtení: 40 minut

Sdílet

11. Přidání dvou koncových bodů do specifikace služby

12. Implementace handlerů nových koncových bodů

13. Otestování druhé varianty služby pomocí Swagger UI i nástroje curl

14. Vylepšení služby – koncový bod pro poslání zprávy

15. Specifikace nového koncového bodu s určením parametrů

16. Demonstrační implementace nového koncového bodu a otestování služby

17. Poslání zprávy přímo přes Swagger UI

18. Čtvrtá varianta služby s koncovým bodem vracejícím data ve formátu JSON

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Mikroslužby založené na REST API

V prvních pěti částech seriálu o mikroslužbách jsme se zabývali především teoretickým popisem vlastní architektury mikroslužeb a taktéž některých základních technologií, které jsou ve světě mikroslužeb často používány. Druhá část tohoto seriálu bude ovšem zaměřena více prakticky, protože si postupně ukážeme způsoby použití jednotlivých technologií. Demonstrační příklady budou většinou naprogramovány v Pythonu a taktéž v jazyku Go. Volba těchto dvou programovacích jazyků samozřejmě není náhodná, protože se s oběma zmíněnými jazyky ve světě mikroslužeb poměrně často setkáme, i když je na tomto místě nutné říci, že se Python od Go v mnoha oblastech odlišuje (nebo jinými slovy – tyto jazyky se vzájemně doplňují). Dále se pochopitelně v této oblasti setkáme s Javou a dalšími programovacími jazyky postavenými nad JVM (především se Scalou), popř. s C#, s JavaScriptem a TypeScriptem (Node.js) a někdy též s jazykem Ruby.

Obrázek 1: Jednotlivé mikroslužby mezi sebou mohou komunikovat například s využitím protokolu HTTP (REST API), STOMP atd. Dnes nás bude zajímat právě HTTP.

Poznámka: z předchozích článků již víme, že právě architektura mikroslužeb umožňuje, aby byly jednotlivé služby, z nichž se celá aplikace postupně složí, vyvinuty v různých programovacích jazycích. Je tedy například snadné identifikovat tu část aplikace, která má velké nároky na výpočetní výkon a tu přepsat z Pythonu (či JavaScriptu nebo TypeScriptu) do Go nebo Javy. Rozdělení monolitické aplikace na menší a snadněji spravovatelné moduly tento postup umožňuje aplikovat po částech a nezávisle na dalších funkcích aplikace.

Nejdříve si ukážeme, jakým způsobem se v Pythonu a Go vytváří služby s rozhraním REST API. Ve skutečnosti se nemusí jednat o nic složitého, protože REST API je postaveno na protokolu HTTP (popř. dnes spíše HTTPS) a při použití vhodných knihoven a frameworků je implementace jednotlivých koncových bodů (endpointů) REST API stejně přímočará jako vytvoření běžného API.

2. Jakou technologii vybrat pro implementaci mikroslužby s REST API?

Samotné mikroslužby mohou být realizovány relativně snadno (alespoň z hlediska použitých technologií, protože business logika může být někdy hodně složitá). Primárním způsobem komunikace mikroslužeb s okolím je v současnosti protokol HTTP(S) a REST; ovšem nesmíme zapomenout ani na protokoly používané při komunikaci s message brokerem (AMQP, MQTT, STOMP, XMPP). Pro samotnou realizaci je možné použít různé programovací jazyky, například (z těch používanějších jazyků) Javu, Python, Ruby, JavaScript (TypeScript) a v neposlední řadě i programovací jazyk Go. Pro každý z těchto jazyků popř. ekosystémů existují knihovny a frameworky určené (mimo dalších použití) právě pro tvorbu mikroslužeb. V následující tabulce jsou některé z těchto knihoven/frameworků vypsány, ovšem pochopitelně se nejedná o úplný přehled, protože mikroslužbu můžeme v případě potřeby naprogramovat například i v čistém céčku s využitím socketů, knihoven 0MQ, nanomsg atd.:

# Jazyk/ekosystém Knihovna či framework
1 Java Vert.x, Spring Boot, Dropwizard, Ninja
2 Python Django REST framework, FlaskRESTful, restless, Falcon
3 Ruby Rails, Grape, Sinatra, Hanami
4 JavaScript/TypeScript Seneca, Koa, LoopBack
5 Go Goa, standardní balíček net/http
6 Clojure Ring, Liberator, Compojure
Poznámka: pravděpodobně jste si všimli, že se mnohdy jedná o „obyčejné“ knihovny poskytující REST API. To je v pořádku, protože mnohdy nic dalšího, než realizaci několika REST API endpointů ani nevyžadujeme (ostatně samotný název „mikroslužby“ evokuje, že si mnohdy vystačíme pouze se základními technologiemi).

Pro ukázku, že webovou službu (či její kostru) lze vytvořit v prakticky v jakémkoli programovacím jazyce si ukažme primitivní službu naprogramovanou v Clojure, v níž se využívá knihovna Clojure Ring:

(ns webapp1.core
    (:gen-class))
 
(require '[ring.adapter.jetty :as jetty])
 
(defn app
    "Funkce predstavujici kostru webove aplikace."
    [request]
    {:status 200
     :headers {"Content-Type" "text/plain"}
     :body "Hello World"})
 
(defn -main
    "Spusteni webove aplikace na portu 8080."
    [& args]
(jetty/run-jetty app {:port 8080}))

Navíc je možné s využitím knihovny Hiccup vygenerovat i HTML stránku z dat reprezentovaných rozšířenými S-výrazy:

(ns htmltest1.core
    (:gen-class))
 
(require '[hiccup.page :as page])
 
(defn html-page
    []
    (page/xhtml
        [:head
            [:title "Hiccup test #1"]
            [:meta {:name "Generator" :content "Clojure"}]
            [:meta {:http-equiv "Content-type" :content "text/html; charset=utf-8"}]
        ]
        [:body
            [:h1 "Hiccup test #1"]
            [:div "Hello world!"]
        ]))
Poznámka: nemusíte se bát – další příklady jsou naprogramovány v Go a Pythonu :-)

3. Jednoduchá aplikace s HTTP serverem naprogramovaná v Go

V programovacím jazyku Go můžeme pro implementaci HTTP serverů použít balíček pojmenovaný výstižně net/http, který je součástí standardní knihovny nainstalované současně s překladačem a dalšími standardními nástroji ekosystému Go (jinými slovy – kromě samotného Go již není vyžadována žádná další komponenta). Jak již název tohoto balíčku napovídá, obsahuje funkce a nové datové typy určené pro práci s protokolem HTTP, a to jak pro klienty, kteří posílají dotazy (request) na servery, tak i pro implementaci vlastních serverů zpracovávajících dotazy a vytvářejících odpovědi (response). Ostatně právě existence tohoto balíčku měla poměrně velký vliv na oblíbenost programovacího jazyka Go pro tvorbu síťově orientovaných aplikací a tím pádem i pro implementaci služeb a mikroslužeb v tomto programovacím jazyku.

Nejužitečnější vlastností balíčku net/http je jeho podpora pro vytvoření skutečného a plnohodnotného HTTP serveru, a to doslova na několika řádcích programového kódu. Základem pro vytvoření HTTP serveru je funkce nazvaná HandleFunc, která nám umožňuje zaregistrovat obslužnou funkci (takzvaný handler) v případě, že je server volán s určitým URL (endpointem). Můžeme si například zaregistrovat handler pro endpoint /:

http.HandleFunc("/", mainEndpoint)

Hlavička funkce HandleFunc z balíčku net/http vypadá takto:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

Povšimněte si, že druhým parametrem této funkce je jiná funkce (onen handler) s hlavičkou:

func MujHandler(ResponseWriter, *Request)

Tomuto handleru se předávají dva objekty – objekt sloužící pro zápis hlavičky i těla odpovědi a objekt s informacemi o požadavku došlého od klienta.

Konkrétně může implementace našeho handleru poslat na výstup (typu ResponseWriter) jednoduchý text, který bude zaslán klientovi v celé HTTP odpovědi (s hlavičkami, stavovým kódem, případnou délkou zprávy, cookies, atd. atd.):

func mainEndpoint(writer http.ResponseWriter, request *http.Request) {
        io.WriteString(writer, "Hello world!\n")
}

Následně již stačí server spustit na určeném portu. My jsme si pro testovací účely zvolili port 8000:

http.ListenAndServe(":8000", nil)
Poznámka: číslo portu by mělo být větší než 1023, protože porty s nižšími čísly vyžadují administrátorská práva a většinou není nutné ani bezpečné spouštět služby s těmito právy administrátora.

Úplná implementace takto jednoduchého HTTP serveru může vypadat takto:

package main
 
import (
        "io"
        "net/http"
)
 
func mainEndpoint(writer http.ResponseWriter, request *http.Request) {
        io.WriteString(writer, "Hello world!\n")
}
 
func main() {
        http.HandleFunc("/", mainEndpoint)
        http.ListenAndServe(":8000", nil)
}

K otestování tohoto příkladu využijeme například známou utilitku curl:

$ curl localhost:8000
 
Hello world!

Popř. pro podrobnější výstup můžeme utilitě curl předat přepínač -v:

$ curl -v localhost:8000
 
* Rebuilt URL to: localhost:8000/
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8000 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:8000
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Wed, 13 Feb 2019 19:31:17 GMT
< Content-Length: 13
< Content-Type: text/plain; charset=utf-8
<
Hello world!
* Connection #0 to host localhost left intact

Nic nám samozřejmě nebrání otestovat naši službu přímo ve webovém prohlížeči:

Obrázek 2: Výsledek zobrazený ve Firefoxu.

Obrázek 3: Výsledek zobrazený v prohlížeči Lynx.

Partner seriálu o mikroslužbách

V IGNUM mají rádi technologie a staví na nich vlastní mikroslužby, díky kterým je registrace a správa domén, hostingů a e-mailů pro zákazníky hračka. Vymýšlet jednoduchá řešení pro obsluhu složitých systémů a uvádět je v život je výzva. Společnost IGNUM miluje mikroslužby a je proto hrdým partnerem tohoto seriálu.

4. Použití protokolu HTTPS namísto HTTP

V této kapitole si ukážeme, jakým způsobem je možné s využitím programovacího jazyka Go a jeho základních knihoven implementovat HTTPS server. Uvidíme, že samotná implementace bude velmi podobná implementaci HTTP serveru, ovšem pro správnou funkčnost protokolu HTTPS budeme muset použít externího nástroje pro vytvoření privátního klíče serveru a jeho certifikátu. Bližší informace o samotném konceptu, na němž je HTTPS postaveno, naleznete například na stránce https://en.wikipedia.org/wiki/HTTPS.

Poznámka: v příkladu budeme používat port 4443 a nikoli obvyklejší port 443. Je tomu tak z toho důvodu, že pro otevření portů s nižšími čísly je nutné mít práva administrátora.

Samotná implementace jednoduchého HTTPS serveru se ve skutečnosti podobá implementaci běžného HTTP serveru. Jediným rozdílem je, že se server bude spouštět odlišným způsobem. Namísto:

http.ListenAndServe(":8000", nil)

použijeme:

http.ListenAndServeTLS(":4443", "server.crt", "server.key", nil)

kde „server.crt“ a „server.key“ jsou soubory, které si vygenerujeme podle návodu uvedeného v navazující kapitole.

Korektnější bude provést kontrolu, zda funkce http.ListenAndServeTLS neskončila s chybou:

err := http.ListenAndServeTLS(":4443", "server.crt", "server.key", nil)
if err != nil {
        log.Fatal("ListenAndServe: ", err)
}

5. Druhá varianta serveru používajícího protokol HTTPS

Implementace HTTPS serveru bude vypadat takto:

package main
 
import (
        "io"
        "log"
        "net/http"
)
 
func mainEndpoint(writer http.ResponseWriter, request *http.Request) {
        writer.Header().Set("Content-Type", "text/plain")
        io.WriteString(writer, "Hello world!\n")
}
 
func main() {
        http.HandleFunc("/", mainEndpoint)
        err := http.ListenAndServeTLS(":4443", "server.crt", "server.key", nil)
        if err != nil {
                log.Fatal("ListenAndServe: ", err)
        }
}

Prozatím ovšem ještě nemáme připraveny všechny potřebné soubory, a to ani na straně serveru, ani na straně klienta. Proto se pokus o zavolání serveru nezdaří:

$ curl -v https://localhost:4443
 
* Rebuilt URL to: https://localhost:4443/
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 4443 (#0)
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* SSLv3, TLS handshake, Client hello (1):
* SSLv3, TLS handshake, Server hello (2):
* SSLv3, TLS handshake, CERT (11):
* SSLv3, TLS alert, Server hello (2):
* SSL certificate problem: self signed certificate
* Closing connection 0
curl: (60) SSL certificate problem: self signed certificate
More details here: http://curl.haxx.se/docs/sslcerts.html
 
curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.

Nyní musíme vygenerovat soubory server.key a server.crt, které bude naše implementace HTTPS serveru používat.

Privátní klíč používaný serverem vygenerujeme s využitím nástroje openssl, který již pravděpodobně máte v systému nainstalovaný:

$ openssl genrsa -out server.key 2048

Výsledkem by měl být soubor server.key obsahující klíč pro 2048bitové RSA:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAyhgV0Gmo0dCkdcEO5X0J//xKD73E+n0pyw7htM/1gPnU9h2X
JYNqFnq0xz9QsxTAPHYLkueW1SNhWT9gq3Sad/M3Cxb6uomB+i0qSk71Q6PkaqHQ
KveSsNNa4lw5DBFVjTD/JPnWhVvKS7v0A266snwmTi18+fRpWZ/TaQN5uQRy0bik
...
...
...
RbBs8QKBgB3dl+NGC+iTPVviPixjFkP5KAcf3Is57Pi0RUgTj4Fmq2q90Scoi6Vv
OzZoo2XHmqAnqxV75OWqA7NiKdBHwWg2O9BupFa+G3uRXgoP7cpCeT9ZoUbbMKww
j49BC9GHmOhlcz3fBT4YE3OgoeM5Fga8sVtWew9YkKe/gBAkR0+Y
-----END RSA PRIVATE KEY-----

Dále, opět nástrojem openssl, vytvoříme soubor s certifikátem s uvedenou platností:

$ openssl req -new -x509 -sha256 -key server.key -out server.crt -days 3650

V tomto okamžiku se bude openssl interaktivně ptát na několik údajů, které jsou v přepisu konverzace vypsány tučně:

You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CZ
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:Kocourkov
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Mestska garda
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:localhost
Email Address []:nikdo@nikde.cz
Poznámka: vyplnit můžete téměř jakékoli údaje, pouze u „Common Name“ ponechejte „localhost“ popř. pravé doménové jméno (pokud ho váš počítač má přiřazené).

Výsledný soubor nazvaný „server.crt“ může vypadat takto:

-----BEGIN CERTIFICATE-----
MIIDlzCCAn+gAwIBAgIJALw/AUKjIONeMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV
BAYTAkNaMRMwEQYDVQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlLb2NvdXJrb3Yx
FjAUBgNVBAoMDU1lc3Rza2EgZ2FyZGExEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0x
...
...
...
rnPxzautFYD++NjhJ/j537I0Lcj9t/DkjvBiECZYkJF8p9dL4+lWZXc27n3RYS6L
7Dj+85WUXwxkfPqhkggGi8jSrZesUDtWw4XFw7bLGOaKTo2JMGxOfxL3RhrFtiMO
4j9Rvz9cr2R6a0Y=
-----END CERTIFICATE-----

HTTPS server již máme připravený (a pravděpodobně i úspěšně spuštěný), takže ještě musíme provést konfiguraci na straně klienta. Nejprve získáme certifikát z běžícího serveru, opět s využitím nástroje openssl, který zkontaktuje HTTPS server a získá od něj všechny potřebné údaje:

$ openssl s_client -showcerts -connect localhost:4443

Výsledek může vypadat následovně – nejprve je zobrazen vlastní certifikát a posléze další metadata:

CONNECTED(00000003)
---
Certificate chain
 0 s:/C=CZ/ST=Some-State/L=Kocourkov/O=Mestska garda/CN=localhost
   i:/C=CZ/ST=Some-State/L=Kocourkov/O=Mestska garda/CN=localhost
-----BEGIN CERTIFICATE-----
MIIDlzCCAn+gAwIBAgIJALw/AUKjIONeMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNV
BAYTAkNaMRMwEQYDVQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlLb2NvdXJrb3Yx
FjAUBgNVBAoMDU1lc3Rza2EgZ2FyZGExEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0x
OTAyMTYyMDE0MTJaFw0yOTAyMTMyMDE0MTJaMGIxCzAJBgNVBAYTAkNaMRMwEQYD
VQQIDApTb21lLVN0YXRlMRIwEAYDVQQHDAlLb2NvdXJrb3YxFjAUBgNVBAoMDU1l
c3Rza2EgZ2FyZGExEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBAMoYFdBpqNHQpHXBDuV9Cf/8Sg+9xPp9KcsO4bTP9YD5
1PYdlyWDahZ6tMc/ULMUwDx2C5LnltUjYVk/YKt0mnfzNwsW+rqJgfotKkpO9UOj
5Gqh0Cr3krDTWuJcOQwRVY0w/yT51oVbyku79ANuurJ8Jk4tfPn0aVmf02kDebkE
ctG4pIsfu6HPfBPyMFgEBXYDiObKfGCEgpnGIeX8Li4n9r8Law45+KEFz4n2Yj3c
Jq77ZLopjV4w4n+JZYNXkK9JeV9twM5PrsYLqrLEvstXqyo/2ccYFtMvTsXx57SY
BEKABLYuPsEzYVzNo2lgtXJxxgcXfS+PrCnH6KhS4c0CAwEAAaNQME4wHQYDVR0O
BBYEFBdmG7K8HXslTnR5OkOLZCGVPD1hMB8GA1UdIwQYMBaAFBdmG7K8HXslTnR5
OkOLZCGVPD1hMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAKYKeOz4
u0er6BQmy72Wc4H9ZjWnXjphfVAC0UK2gz7UHXDnyzfrBKR6FkVbeiIUjBzbrWG5
xUoHcZsfayefOEEpqcAyKpa8CRkbissHF6qtFZArt+cOWwTmmPfYQxfa9KqVP13L
FcZqcchyvTLdNTGD5ZBtLI9B5Pcm4a7vgEdMqdJb++FpSNhW9H2P0wvfhTK7Mh6/
rnPxzautFYD++NjhJ/j537I0Lcj9t/DkjvBiECZYkJF8p9dL4+lWZXc27n3RYS6L
7Dj+85WUXwxkfPqhkggGi8jSrZesUDtWw4XFw7bLGOaKTo2JMGxOfxL3RhrFtiMO
4j9Rvz9cr2R6a0Y=
-----END CERTIFICATE-----
---
Server certificate
subject=/C=CZ/ST=Some-State/L=Kocourkov/O=Mestska garda/CN=localhost
issuer=/C=CZ/ST=Some-State/L=Kocourkov/O=Mestska garda/CN=localhost
---
No client certificate CA names sent
---
SSL handshake has read 1529 bytes and written 421 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: 1F74CA2806A25B6AE774B7A0D4E470A477D16B73130EAB527C03FE861086B076
    Session-ID-ctx:
    Master-Key: 555000EA285A2EEFE95D5268756ED98CC71711075F8036251EC6B34494C7ED8F6861EDDDA842BC847922F536AC9CF0EA
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket:
    0000 - 71 d2 38 c8 ec 5d e7 5e-c7 fe a7 f5 d4 33 03 e8   q.8..].^.....3..
    0010 - e9 ba 8d ff f2 1e e7 9f-8e 66 9b 0e 2b 34 eb db   .........f..+4..
    0020 - 83 c1 4b 96 f7 a4 67 71-26 3a a5 2d 65 2e 08 ae   ..K...gq&:.-e...
    0030 - 84 38 3f bf 90 2e 04 0a-62 25 aa 0e 86 ca 31 4a   .8?.....b%....1J
    0040 - b7 2a 1b 1a b7 b0 b2 d9-d5 3c f4 9e 39 37 a1 69   .*.......<..97.i
    0050 - 6c ac 2c 8b 83 d0 25 53-da 7c 43 17 4d 55 d6 fc   l.,...%S.|C.MU..
    0060 - 7a 55 2f 74 bd 6a e2 6f-59 b0 cc 16 d7 e0 a9 14   zU/t.j.oY.......
    0070 - 71 35 d4 62 27 85 93 f7-                          q5.b'...
 
    Start Time: 1550348405
    Timeout   : 300 (sec)
    Verify return code: 18 (self signed certificate)
---

Nástroj budeme muset ukončit klávesovou zkratkou Ctrl+C.

Pro klienta je nejjednodušší přesměrovat výstup z předchozího volání nástroje openssl do souboru, který bývá nazván „certs.pem“:

$ openssl s_client -showcerts -connect localhost:4443 > certs.pem
$ openssl s_client -showcerts -connect localhost:4443 > certs.pem

To je ze strany klienta vše – klient pouze potřebuje pro každé volání použít soubor „certs.pem“ s certifikátem serveru, aby ho mohl ověřit.

Nyní již máme vše připravené pro to, aby se klient mohl připojit k serveru s využitím certifikátu uloženého v lokálním souboru certs.pem. Příkaz volající utilitu curl bude vypadat následovně:

$ curl -v --cacert certs.pem  https://localhost:4443

Po spuštění nástroje curl by se měl klient připojit k serveru s využitím protokolu HTTPS, ověřit certifikát a následně přečíst odpověď serveru („Hello world!“):

* Rebuilt URL to: https://localhost:4443/
* Hostname was NOT found in DNS cache
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 4443 (#0)
* successfully set certificate verify locations:
*   CAfile: certs.pem
  CApath: /etc/ssl/certs
* SSLv3, TLS handshake, Client hello (1):
} [data not shown]
* SSLv3, TLS handshake, Server hello (2):
{ [data not shown]
* SSLv3, TLS handshake, CERT (11):
{ [data not shown]
* SSLv3, TLS handshake, Server key exchange (12):
{ [data not shown]
* SSLv3, TLS handshake, Server finished (14):
{ [data not shown]
* SSLv3, TLS handshake, Client key exchange (16):
} [data not shown]
* SSLv3, TLS change cipher, Client hello (1):
} [data not shown]
* SSLv3, TLS handshake, Finished (20):
} [data not shown]
* SSLv3, TLS change cipher, Client hello (1):
{ [data not shown]
* SSLv3, TLS handshake, Finished (20):
{ [data not shown]
* SSL connection using ECDHE-RSA-AES128-GCM-SHA256
* Server certificate:
*        subject: C=CZ; ST=Some-State; L=Kocourkov; O=Mestska garda; CN=localhost
*        start date: 2019-02-16 20:14:12 GMT
*        expire date: 2029-02-13 20:14:12 GMT
*        common name: localhost (matched)
*        issuer: C=CZ; ST=Some-State; L=Kocourkov; O=Mestska garda; CN=localhost
*        SSL certificate verify ok.
> GET / HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:4443
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Sat, 16 Feb 2019 20:20:29 GMT
< Content-Length: 13
<
{ [data not shown]
100    13  100    13    0     0   1106      0 --:--:-- --:--:-- --:--:--  1181
* Connection #0 to host localhost left intact
Hello world!

Vidíme, že certifikát byl skutečně použit.

Pokud se používá webový prohlížeč s GUI, bývá práce s certifikáty snazší:

Obrázek 4: Informace o certifikátu při prvním přístupu k našemu serveru. Certifikát jsme si podepsali sami a nebyl potvrzen žádnou certifikační autoritou.

Obrázek 5: Zobrazení dalších informací o certifikátu – tyto informace jsme zadali při jeho vytváření.

Po potvrzení, že certifikátu důvěřujeme, se již zobrazí kýžená webová stránka.

6. Použití frameworku Flask a Connexion při tvorbě služeb s REST API v Pythonu

Ve druhé části dnešního článku o mikroslužbách si ukážeme, jakým způsobem je možné vyvinout jednoduchou (mikro)službu s rozhraním REST API, tentokrát však nikoli v jazyku Go, ale v Pythonu. Zatímco v případě programovacího jazyka Go byl ukázán spíše nízkoúrovňový přístup, v němž jsme jednotlivé koncové body registrovali ručně (a vlastně jsme vůbec neřešili případ, kdy je část cesty v URL proměnná, což je v REST API časté), v Pythonu použijeme frameworky pojmenované Flask a Connexion, které umožňují, aby celé REST API bylo deklarativně popsáno v samostatném souboru swagger.yaml. V tomto souboru mohou být uvedeny všechny důležité metainformace o REST API, včetně jmen funkcí, které implementují jednotlivé handlery požadavků.

To však není zdaleka vše, protože framework Connexion umožňuje, aby služba obsahovala i automaticky generované webové uživatelské rozhraní s popisem všech koncových bodů, které je možné z tohoto rozhraní přímo volat. Nemusíme se tedy spoléhat na externí utilitu curl, protože samotná služba obsahuje vše potřebné pro své vlastní otestování.

Obrázek 6: Editor souborů s popisem REST API.

Poznámka: samotný soubor swagger.yaml s popisem REST API, včetně všech potřebných metainformací, můžeme pochopitelně vytvořit v jakémkoli textovém editoru. Existují ovšem i specializované webové nástroje, které tuto činnost mohou zjednodušit, zejména pro ty uživatele, kteří formát YAML ani specifikaci OpenAPI příliš neznají nebo s ní teprve začínají pracovat. Jedním z těchto nástrojů je https://editor.swagger.io/, v němž je možné vytvořit popis REST API a následně si nechat vygenerovat kostru celé aplikace ve zvoleném programovacím jazyce.

7. Nejjednodušší aplikace založená pouze na frameworku Flask

S využitím frameworku Flask je vytvoření jednoduché webové aplikace (služby) s malým množstvím koncových bodů velmi snadné. Ostatně se stačí podívat na následující příklad, který po svém spuštění inicializuje HTTP server, který po poslání požadavku na koncový bod / vrátí odpověď s textem „Hello, world!“. Povšimněte si, jakým způsobem je s využitím dekorátoru určena vazba mezi koncovým bodem a handlerem (funkcí zavolanou při příchodu požadavku). Samotný handler v tom nejjednodušším případě pouze vrátí řetězec, který je považován za tělo odpovědi (HTTP kód bude 200 OK):

from flask import Flask
 
app = Flask(__name__)
 
@app.route('/')
def hello_world():
    return 'Hello, World!\n'
export FLASK_APP=01_basic_app.py
PYTHONDONTWRITEBYTECODE=1 python3 -m flask run
Poznámka: jedná se skutečně o to nejjednodušší možné řešení, v němž například vůbec nerozdělujeme aplikaci podle vzoru MVC. U větších aplikací je to většinou nutné, u malých mikroslužeb teoreticky ne, ovšem MVC je i v této oblasti dobrým návrhovým vzorem.

8. Specifikace API s využitím Swaggeru

V předchozím textu jsme se zmínili o tom, že popis celého rozhraní REST API je uložen v souborech se jménem swagger.yaml. Koncovka .yaml je pochopitelná, protože se používá formát YAML (YAML Ain't Markup Language). Ovšem proč se soubor jmenuje swagger? Původně se totiž popis REST API v tomto formátu objevil ve frameworku Swagger, takže se pro něj vžilo označení „Swagger Specification“. Ovšem samotný formát metadat s popisem REST API se později přejmenoval ze „Swagger Specification“ na „OpenAPI Specification“; současně se o vývoj tohoto formátu a nástrojů okolo něho postavených začala starat OpenAPI Initiative. Bližší informace o OpenAPI je možné nalézt na stránce https://swagger.io/specification/, popř. pro verzi 2.0 přímo na GitHubu na adrese https://github.com/OAI/OpenAPI-Specification/blob/master/ver­sions/2.0.md. Základní informace a seznam dalších informačních zdrojů je uveden i na Wikipedii, konkrétně na stránce https://en.wikipedia.org/wi­ki/OpenAPI_Specification. Dnes existuje velké množství nástrojů pro generování a editaci popisu REST API, pro vytvoření serveru či klienta na základě tohoto popisu apod. Seznam těchto nástrojů je dostupný na stránkách https://github.com/OAI/OpenAPI-Specification/blob/master/IM­PLEMENTATIONS.md#implemen­tations a https://github.com/APIs-guru/awesome-openapi3.

Poznámka: v dále popsaných demonstračních příkladech budeme používat OpenAPI 2.0, která se od novější specifikace OpenAPI 3.0 v některých maličkostech odlišuje. Týká se to zejména popisu dat, která mohou být přenesena v těle požadavku (request body). Tento popis použijeme ve třetím a čtvrtém demonstračním příkladu (první dva příklady by měly být nezávislé na verzi OpenAPI).

9. Kostra služby

Nyní si ukážeme kostru webové služby vytvořenou s využitím frameworků Flask a taktéž Connexion. Druhý z těchto frameworků mj. poslouží pro zobrazení webové stránky, přes kterou bude možné webovou službu ovládat. Kostra celé služby obsahuje jen minimum souborů a má jednoduchou strukturu:

├── requirements.in
├── requirements.txt
├── run.sh
├── service1.py
├── setup.cfg
└── swagger
    └── swagger.yaml

Význam jednotlivých souborů:

Soubor Stručný popis
requirements.in seznam balíčků, na kterých služba závisí
requirements.txt balíčky i s uvedením verze (automaticky generováno z předchozího souboru)
run.sh skript pro spuštění služby
service1.py vlastní implementace webové služby
setup.cfg pomocný soubor s konfigurací používaný různými nástroji pro Python
swagger/swagger.yaml metainformace o REST API

Obsah souboru requirements.in se seznamem balíčků, na kterých služba závisí:

connexion
jsonschema

Obsah souboru requirements.txt obsahujícího balíčky i s uvedením verze (automaticky generováno z předchozího souboru):

connexion==1.1.15
jsonschema==2.5.1

Skript pro inicializaci virtuálního prostředí Pythonu a spuštění webové služby:

#!/bin/bash -ex
 
export NOVENV=0
function prepare_venv() {
    virtualenv -p python3 venv && source venv/bin/activate && python3 "$(which pip3)" install -r requirements.txt
}
 
[ "$NOVENV" == "1" ] || prepare_venv || exit 1
 
PYTHONDONTWRITEBYTECODE=1 python3 service1.py

10. První služba naprogramovaná v Pythonu

Implementace webové služby (prozatím bez koncových bodů) vypadá následovně. Můžeme v něm vidět pouze inicializaci serveru, předání adresáře obsahujícího soubor swagger.yaml a spuštění serveru na portu 8080:

#!/usr/bin/env python3
 
import connexion
 
 
def main():
    """Start the Flask app."""
    app = connexion.App(__name__, specification_dir='./swagger/')
    app.add_api('swagger.yaml', arguments={'title': 'Service 1'})
    app.run(port=8080)
 
 
if __name__ == '__main__':
    main()

Mnohem zajímavější jsou metainformace o REST API. Ty jsou zapsány v souboru s formátem YAML, v němž se jednotlivé poduzly specifikují odsazením a dvojtečkou, podobně jako v Pythonu. Povšimněte si, že používáme specifikaci OpenAPI verze 2.0:

---
swagger: "2.0"
info:
  description: "Simple REST API service."
  version: "1.0.0"
  title: "Service 1"
  contact:
    email: "tisnik@somewhere.else"
  license:
    name: "Apache 2.0"
    url: "http://www.apache.org/licenses/LICENSE-2.0.html"
basePath: "/api/v1"
tags:
- name: "Service settings"
  description: "The service settings"
schemes:
- "https"
- "http"
paths: {}
definitions: {}
externalDocs:
  description: "Find out more about Swagger"
  url: "http://swagger.io"

První demonstrační příklad založený na frameworku Flask a Connexion spustíme přes skript run.sh. Tento skript nejdříve připraví virtuální prostředí Pythonu a následně inicializuje server. Na posledním řádku je informace o portu, na kterém server naslouchá požadavkům od klientů:

$ ./run.sh
 
+ python3 service1.py
 * Serving Flask app "service1" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)

Po spuštění prvního demonstračního příkladu si můžeme vyzkoušet, zda služba skutečně přijímá požadavky. Prozatím sice nemáme žádné koncové body REST API, ovšem minimálně by mělo být možné získat popis REST API, tentokrát pro změnu ve formátu JSON:

$ curl http://localhost:8080/api/v1/swagger.json

Výsledkem předchozího volání by měla být odpověď s nenaformátovaným JSONem:

{"basePath":"/api/v1","definitions":{},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"},"info":{"contact":{"email":"tisnik@somewhere.else"},"description":"Simple REST API service.","license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"title":"Service 1","version":"1.0.0"},"paths":{},"schemes":["https","http"],"swagger":"2.0","tags":[{"description":"The service settings","name":"Service settings"}]}

Po naformátování:

{
  "basePath": "/api/v1",
  "definitions": {

  },
  "externalDocs": {
    "description": "Find out more about Swagger",
    "url": "http://swagger.io"
  },
  "info": {
    "contact": {
      "email": "tisnik@somewhere.else"
    },
    "description": "Simple REST API service.",
    "license": {
      "name": "Apache 2.0",
      "url": "http://www.apache.org/licenses/LICENSE-2.0.html"
    },
    "title": "Service 1",
    "version": "1.0.0"
  },
  "paths": {

  },
  "schemes": [
    "https",
    "http"
  ],
  "swagger": "2.0",
  "tags": [
    {
      "description": "The service settings",
      "name": "Service settings"
    }
  ]
}

Samozřejmě se můžeme podívat i na všechny hlavičky posílané klientem na server i serverem zpět na klienta:

$ curl -v http://localhost:8080/api/v1/swagger.json

Směr komunikace se pozná podle prefixů jednotlivých řádků. Požadavek klienta začíná znakem >, odpověď serveru naopak znakem <:

*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/v1/swagger.json HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.53.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Content-Length: 475
< Server: Werkzeug/0.15.4 Python/3.6.3
< Date: Wed, 10 Jul 2019 12:03:07 GMT
<
{"basePath":"/api/v1","definitions":{},"externalDocs":{"description":"Find out more about Swagger","url":"http://swagger.io"},"info":{"contact":{"email":"tisnik@somewhere.else"},"description":"Simple REST API service.","license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"title":"Service 1","version":"1.0.0"},"paths":{},"schemes":["https","http"],"swagger":"2.0","tags":[{"description":"The service settings","name":"Service settings"}]}
* Closing connection 0

Obrázek 7: Zobrazení popisu služby přímo v prohlížeči.

11. Přidání dvou koncových bodů do specifikace služby

Nyní do webové služby přidáme dva koncové body /liveness a /readiness. Povšimněte si, že vazba na handlery (konkrétní funkce zapsané plným jménem) je provedena přímo v souboru swagger.yaml a nikoli ve zdrojovém kódu aplikace:

  /liveness:
    get:
      tags: [Service settings]
      operationId: "service2.get_liveness"
      summary: "Get service liveness"
      responses:
        200:
          description: Service is alive
  /readiness:
    get:
      tags: [Service settings]
      operationId: "service2.get_readiness"
      summary: "Get service readiness"
      responses:
        200:
          description: Service is ready

Úplný obsah souboru swagger.yaml vypadá následovně:

---
swagger: "2.0"
info:
  description: "Simple REST API service."
  version: "1.0.0"
  title: "Service 2"
  contact:
    email: "tisnik@somewhere.else"
  license:
    name: "Apache 2.0"
    url: "http://www.apache.org/licenses/LICENSE-2.0.html"
basePath: "/api/v1"
tags:
- name: "Service settings"
  description: "The service settings"
schemes:
- "https"
- "http"
paths:
  /liveness:
    get:
      tags: [Service settings]
      operationId: "service2.get_liveness"
      summary: "Get service liveness"
      responses:
        200:
          description: Service is alive
  /readiness:
    get:
      tags: [Service settings]
      operationId: "service2.get_readiness"
      summary: "Get service readiness"
      responses:
        200:
          description: Service is ready
definitions: {}
externalDocs:
  description: "Find out more about Swagger"
  url: "http://swagger.io"

12. Implementace handlerů nových koncových bodů

Samotná implementace obou handlerů (umístěná ve zdrojovém souboru service2.py) je přímočará, protože ve skutečnosti vrátíme prázdnou strukturu (objekt) a stavový kód HTTP bude za všech okolností 200 OK:

def health_check():
    """Check the health status of the service."""
    return {}, 200
 
 
def get_readiness():
    """Get service readiness status."""
    return health_check()
 
 
def get_liveness():
    """Get service liveness status."""
    return health_check()
Poznámka: povšimněte si, že se jedná o běžné funkce, které dokonce ani nemusí obsahovat žádný dekorátor.

Úplný zdrojový kód druhého příkladu vytvořeného v Pythonu vypadá následovně:

#!/usr/bin/env python3
 
import connexion
 
def health_check():
    """Check the health status of the service."""
    return {}, 200
 
 
def get_readiness():
    """Get service readiness status."""
    return health_check()
 
 
def get_liveness():
    """Get service liveness status."""
    return health_check()
 
 
def main():
    """Start the Flask app."""
    app = connexion.App(__name__, specification_dir='./swagger/')
    app.add_api('swagger.yaml', arguments={'title': 'Service 2'})
    app.run(port=8080)
 
 
if __name__ == '__main__':
    main()

13. Otestování druhé varianty služby pomocí Swagger UI i nástroje curl

Druhý demonstrační příklad již svým uživatelům (tedy klientům) nabízí dva koncové body /api/v1/liveness a /api/v1/readiness. Tyto koncové body by měly vracet prázdnou strukturu ve formátu JSON a stavový kód HTTP odpovědi by měl být 200 OK, tj. informace o tom, že požadavek byl korektní a server ho správně zpracoval:

$ curl http://localhost:8080/api/v1/liveness
{}

I druhý koncový bod by se měl chovat naprosto stejným způsobem:

$ curl http://localhost:8080/api/v1/readiness
{}

Můžeme se pochopitelně přesvědčit i o HTTP stavovém kódu HTTP odpovědi (viz též https://en.wikipedia.org/wi­ki/List_of_HTTP_status_co­des). Pro zobrazení celé odpovědi, samozřejmě včetně HTTP kódu, použijeme přepínač -v:

$ curl -v http://localhost:8080/api/v1/liveness
 
*   Trying ::1...
* TCP_NODELAY set
* connect to ::1 port 8080 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /api/v1/liveness HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.53.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: application/json
< Content-Length: 3
< Server: Werkzeug/0.15.4 Python/3.6.3
< Date: Wed, 10 Jul 2019 12:56:16 GMT
<
{}
* Closing connection 0

Zajímavější ovšem bude přesměrovat webový prohlížeč na adresu /api/v1/ui. Na této adrese se nachází dynamicky generovaná HTML stránka obsahující jak popis REST API (všech koncových bodů), tak i nástroje, které nám umožní tyto body zavolat:

Obrázek 8: Popis služby se zobrazením koncových bodů.

Obrázek 9: Zobrazení podrobnějších informací o vybraném koncovém bodu.

Obrázek 10: Otestování koncového bodu přímo z prohlížeče.

14. Vylepšení služby – koncový bod pro poslání zprávy

Webovou službu ještě vylepšíme, a to přidáním koncového bodu sloužícího pro poslání zprávy vybranému uživateli (ve skutečnosti se zpráva nikam nepošle, ovšem koncový bod bude existovat). Přitom bude služba kontrolovat, zda zadaný uživatel existuje a zda mu lze poslat zprávu. Jedná se o komplikaci služby, protože budeme muset:

  1. Přečíst parametr (jméno uživatele) předaného přes URL
  2. Přečíst tělo požadavku, protože bude obsahovat text zprávy
  3. Zkontrolovat oba parametry a v případě chyby vrátit vhodný stavový kód HTTP
  4. Zkontrolovat existenci příjemce, opět s navrácením vhodného stavového kódu
  5. Zkontrolovat, jestli příjemce může zprávy přijímat

15. Specifikace nového koncového bodu s určením parametrů

Nový koncový bod je nakonfigurován následujícím způsobem:

  /message/{recipient}:
    post:
      tags: [Basic operations]
      operationId: "service3.send_message"
      summary: "Post a message"
      consumes:
      - "text/plain"
      produces:
      - "application/json"
      parameters:
      - name: "recipient"
        in: "path"
        description: "The recipient for the message"
        required: true
        type: "string"
      - name: "message"
        in: body
        description: "Message body"
        required: true
        schema:
          type: string
      responses:
        200:
          description: "Successful operation"
        400:
          description: "Invalid recipient supplied"
        404:
          description: "The specified recipient was not found"
        405:
          description: "Not allowed - it is not allowed to send the message to selected recipient"
        500:
          description: "Any other failure"

Povšimněte si, jak vypadá specifikace parametru předaného přes URL (ve skutečnosti se nejedná o skutečný parametr, ale o součást URL). Dále je specifikováno, že se v těle požadavku očekává běžný text (plain text), zatímco v těle odpovědi bude JSON. Oba parametry (příjemce, text zprávy) jsou dále podrobněji popsány a dokonce je pro uživatele REST API specifikováno, které HTTP kódy může odpověď obsahovat a co přesně v kontextu tohoto koncového bodu znamenají.

16. Demonstrační implementace nového koncového bodu a otestování služby

Do zdrojového kódu příkladu přidáme novou funkci, které se při jejím zavolání automaticky předají oba parametry popsané v souboru swagger.yaml. Kromě toho vytvoříme dvojici n-tic se seznamem všech příjemců i se seznamem příjemců, kterým je dovoleno zprávy posílat:

KNOWN_RECIPIENTS = ("Puchmajer", "Meyer", "Pihrt", "Jason", "Drson", "Trachta", "Fristensky")
ALLOWED_RECIPIENTS = ("Jason", "Drson", "Trachta", "Fristensky")
 
 
def send_message(recipient, message):
    """Send a message to selected recipient."""
    if not recipient:
        return {"Status": "error",
                "Reason": "No recipient supplied"}, 400
 
    if not message:
        return {"Status": "error",
                "Reason": "Message is empty"}, 400
 
    if recipient not in KNOWN_RECIPIENTS:
        return {"Status": "not found",
                "Reason": "The specified recipient was not found"}, 404
 
    if recipient not in ALLOWED_RECIPIENTS:
        return {"Status": "forbidden",
                "Reason": "Not allowed - it is not allowed to send the message to selected recipient"}, 405
 
    try:
        # kod pro skutecne poslani zpravy
        return {"Status": "ok"}, 200
    except Exception as e:
        return {"Status": "error", "Reason": str(e)}, 500

Přístup ke koncovým bodům, ke kterým se přistupuje metodou POST, PUT či DELETE, je nepatrně složitější, než je tomu u metody GET. Použitou metodu totiž musíme specifikovat, a to přepínačem -X metoda. První pokus by tedy mohl vypadat následovně:

$ curl -X POST http://localhost:8080/api/v1/message/Drson

V tomto případě ovšem server (zcela podle očekávání) odpoví, že nebylo specifikováno tělo požadavku, v němž měla být uložena zpráva:

{
  "Reason": "Message is empty",
  "Status": "error"
}

Celý požadavek je tedy nutné strukturovat odlišně. Nejdříve nastavíme typ dat v požadavku:

--header 'Content-Type: text/plain'

Následně (což ovšem není v tomto případě povinné) určíme, v jakém formátu očekáváme odpověď:

--header 'Accept: application/json'

Samotné tělo požadavku s textem zprávy se specifikuje přepínačem -d (ovšem musíme si dát pozor na to, aby nezačínalo zavináčem):

-d 'Transfer Of Usd $21,500.000{Twenty - One Million, Five Hundred Thousand Us Dollars Only. I am a member of the Federal Government Of Nigerian National Petroleum Corporation (N.N.P.C). Sometime ago, a contract was awarded to a foreign firm in the Petroleum Trust Fund (P.T.F.) BY MY COMMITTEE'

Celé volání nástroje curl tedy může vypadat následovně:

$ curl -X POST --header 'Content-Type: text/plain' --header 'Accept: application/json' -d 'Transfer Of Usd $21,500.000{Twenty - One Million, Five Hundred Thousand Us Dollars Only. I am a member of the Federal Government Of Nigerian National Petroleum Corporation (N.N.P.C). Sometime ago, a contract was awarded to a foreign firm in the Petroleum Trust Fund (P.T.F.) BY MY COMMITTEE' 'http://localhost:8080/api/v1/message/Drson'

S výsledkem:

{
  "Status": "ok"
}
Poznámka: zavináč na začátku zprávy způsobí problémy, protože nástroj curl v tomto případě očekává, že se bude jednat o jméno souboru s daty, které se mají poslat.

17. Poslání zprávy přímo přes Swagger UI

Zprávu samozřejmě můžeme poslat i přes UI. Postup je následující:

Obrázek 11: Popis třetí varianty webové služby.

Obrázek 12: Popis koncového bodu /message.

Obrázek 13: Oba parametry jsou povinné a je je nutné vyplnit.

Obrázek 14: Pokus o poslání zprávy neznámému příjemci.

Obrázek 15: Úspěšné doručení zprávy.

18. Čtvrtá varianta služby s koncovým bodem vracejícím data ve formátu JSON

Jen pro úplnost si demonstrační příklad rozšíříme o další koncový bod, který bude sloužit pro získání statistiky o tom, kolik zpráv bylo jednotlivým příjemcům prozatím posláno. Při poslání zprávy se informace o příjemci zaznamená:

counter = Counter()
 
 
 
def send_message(recipient, message):
    """Send a message to selected recipient."""
    if not recipient:
        return {"Status": "error",
                "Reason": "No recipient supplied"}, 400
 
    if not message:
        return {"Status": "error",
                "Reason": "Message is empty"}, 400
 
    if recipient not in KNOWN_RECIPIENTS:
        return {"Status": "not found",
                "Reason": "The specified recipient was not found"}, 404
 
    if recipient not in ALLOWED_RECIPIENTS:
        return {"Status": "forbidden",
                "Reason": "Not allowed - it is not allowed to send the message to selected recipient"}, 405
 
    try:
        # kod pro skutecne poslani zpravy
        counter[recipient] += 1
        return {"Status": "ok"}, 200
    except Exception as e:
        return {"Status": "error", "Reason": str(e)}, 500

Implementace logiky nového koncového bodu je v tomto případě opět přímočará:

def message_statistic():
    """Returns message statistic."""
    return counter

Nový koncový bod samozřejmě budeme muset zaregistrovat a popsat:

---
swagger: "2.0"
info:
  description: "Simple REST API service."
  version: "1.0.0"
  title: "Service 4"
  contact:
    email: "tisnik@somewhere.else"
  license:
    name: "Apache 2.0"
    url: "http://www.apache.org/licenses/LICENSE-2.0.html"
basePath: "/api/v1"
tags:
- name: "Basic operations"
  description: "Basic operations"
- name: "Service settings"
  description: "The service settings"
schemes:
- "https"
- "http"
paths:
  /liveness:
    get:
      tags: [Service settings]
      operationId: "service4.get_liveness"
      summary: "Get service liveness"
      responses:
        200:
          description: Service is alive
  /readiness:
    get:
      tags: [Service settings]
      operationId: "service4.get_readiness"
      summary: "Get service readiness"
      responses:
        200:
          description: Service is ready
  /message_statistic:
    get:
      tags: [Basic operations]
      operationId: "service4.message_statistic"
      summary: "Returns basic statistic about sent messages"
      responses:
        200:
          description: Query was successful
      produces:
      - "application/json"
  /message/{recipient}:
    post:
      tags: [Basic operations]
      operationId: "service4.send_message"
      summary: "Post a message"
      consumes:
      - "text/plain"
      produces:
      - "application/json"
      parameters:
      - name: "recipient"
        in: "path"
        description: "The recipient for the message"
        required: true
        type: "string"
      - name: "message"
        in: body
        description: "Message body"
        required: true
        schema:
          type: string
      responses:
        200:
          description: "Successful operation"
        400:
          description: "Invalid recipient supplied"
        404:
          description: "The specified recipient was not found"
        405:
          description: "Not allowed - it is not allowed to send the message to selected recipient"
        500:
          description: "Any other failure"
definitions: {}
externalDocs:
  description: "Find out more about Swagger"
  url: "http://swagger.io"

Nyní si můžeme službu spustit, poslat několik zpráv a následně si zobrazit statistiku:

$ curl http://localhost:8080/api/v1/message_statistic

S výsledkem:

{
  "Drson": 1,
  "Jason": 3
}

Totéž lze provést přes Swagger UI:

Obrázek 16: Použití nového koncového bodu ze Swagger UI.

Diners Vánoce 2019

Obrázek 17: Swagger editor dokonce dokáže vygenerovat celou kostru aplikace.

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes použitých demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/mi­croservices (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Příklad Popis příkladu Cesta
1 service1.go jednoduchá aplikace s HTTP serverem https://github.com/tisnik/mi­croservices/blob/master/REST-API/Go/service1.go
2 service2.go použití HTTPS namísto HTTP https://github.com/tisnik/mi­croservices/blob/master/REST-API/Go/service2.go
       
3 service1 kostra webové služby naprogramované v Pythonu https://github.com/tisnik/mi­croservices/blob/master/REST-API/Python/service1
4 service2 webová služba v Pythonu s dvojicí koncových bodů https://github.com/tisnik/mi­croservices/blob/master/REST-API/Python/service2
5 service3 koncový bod vyžadující metodu POST https://github.com/tisnik/mi­croservices/blob/master/REST-API/Python/service3
6 service4 koncový bod se statistikou posílání zpráv https://github.com/tisnik/mi­croservices/blob/master/REST-API/Python/service4
       
7 webapp1 jednoduchá služba naprogramovaná v Clojure https://github.com/tisnik/mi­croservices/blob/master/REST-API/Clojure/webapp1
8 webapp2 další jednoduchá služba naprogramovaná v Clojure https://github.com/tisnik/mi­croservices/blob/master/REST-API/Clojure/webapp2

20. Odkazy na Internetu

  1. OpenAPI Initiative (OAI)
    https://www.openapis.org/
  2. OpenAPI Implementations
    https://github.com/OAI/OpenAPI-Specification/blob/master/IM­PLEMENTATIONS.md#implemen­tations
  3. awesome-openapi3
    https://github.com/APIs-guru/awesome-openapi3
  4. Swagger (software)
    https://en.wikipedia.org/wi­ki/Swagger_(software)
  5. Editor pro swagger soubory s popisem REST API
    http://editor.swagger.io/#
  6. YAML
    https://yaml.org/
  7. YAML na Wikipedii
    https://en.wikipedia.org/wiki/YAML
  8. Flask na PyPi
    https://pypi.org/project/Flask/
  9. Connexion na PyPi
    https://pypi.org/project/connexion/
  10. Werkzeug na PyPi
    https://pypi.org/project/Werkzeug/
  11. gdbgui 0.7.8.3: browser-based gdb frontend using Flask and JavaScript to visually debug C, C++, Go, or Rust
    https://pypi.python.org/pypi/gdbgui
  12. Alertmanager
    https://prometheus.io/doc­s/alerting/alertmanager/
  13. Grafana support for Prometheus
    https://prometheus.io/doc­s/visualization/grafana/
  14. goa
    https://stackshare.io/goa
  15. goa (GitHub)
    https://github.com/goadesign/goa
  16. Grafana support for Prometheus
    https://prometheus.io/doc­s/visualization/grafana/
  17. Useful Tools for Managing Complexity of Microservice Architecture
    https://blog.byndyusoft.com/useful-tools-for-managing-complexity-of-microservice-architecture-109a2289acc
  18. Three pillars of microservice culture
    https://www.oreilly.com/ideas/three-pillars-of-microservice-culture
  19. Prometheus: from metrics to insight
    https://prometheus.io/
  20. Docker Swarm
    https://docs.docker.com/swarm/
  21. Kubernetes: production-Grade Container Orchestration
    https://kubernetes.io/
  22. 29 Top Tools for Building Microservices on All Levels
    https://dzone.com/articles/30top-tools-for-building-microservices-on-all-leve
  23. The 8 best open-source tools for building microservice apps
    https://techbeacon.com/enterprise-it/8-best-open-source-tools-building-microservice-apps
  24. Consul
    https://www.consul.io/
  25. Apache ZooKeeper
    https://zookeeper.apache.org/
  26. ZooKeeper: Because Coordinating Distributed Systems is a Zoo
    http://zookeeper.apache.or­g/doc/current/index.html
  27. ZooKeeper: A Distributed Coordination Service for Distributed Applications
    http://zookeeper.apache.or­g/doc/current/zookeeperOver­.html
  28. Understanding Kafka with Legos (video)
    https://www.youtube.com/wat­ch?v=Q5wOegcVa8E
  29. Apache Kafka Tutorial For Beginners (video)
    https://www.youtube.com/wat­ch?v=U4y2R3v9tlY
  30. Franz Kafka (Wikipedia)
    https://en.wikipedia.org/wi­ki/Franz_Kafka
  31. NATS
    https://nats.io/about/
  32. NATS Streaming Concepts
    https://nats.io/documenta­tion/streaming/nats-streaming-intro/
  33. NATS Streaming Server
    https://nats.io/download/nats-io/nats-streaming-server/
  34. NATS Introduction
    https://nats.io/documentation/
  35. NATS Client Protocol
    https://nats.io/documenta­tion/internals/nats-protocol/
  36. NATS Messaging (Wikipedia)
    https://en.wikipedia.org/wi­ki/NATS_Messaging
  37. Stránka Apache Software Foundation
    http://www.apache.org/
  38. Logstash
    https://www.elastic.co/pro­ducts/logstash
  39. Elasticsearch
    https://www.elastic.co/pro­ducts/elasticsearch
  40. Understanding When to use RabbitMQ or Apache Kafka
    https://content.pivotal.i­o/blog/understanding-when-to-use-rabbitmq-or-apache-kafka
  41. Part 1: Apache Kafka for beginners – What is Apache Kafka?
    https://www.cloudkarafka.com/blog/2016–11–30-part1-kafka-for-beginners-what-is-apache-kafka.html
  42. What are some alternatives to Apache Kafka?
    https://www.quora.com/What-are-some-alternatives-to-Apache-Kafka
  43. What is the best alternative to Kafka?
    https://www.slant.co/opti­ons/961/alternatives/~kaf­ka-alternatives
  44. Apache Flume
    https://flume.apache.org/index.html
  45. Snare
    https://www.snaresolutions.com/
  46. The Log: What every software engineer should know about real-time data's unifying abstraction
    https://engineering.linke­din.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying
  47. A super quick comparison between Kafka and Message Queues
    https://hackernoon.com/a-super-quick-comparison-between-kafka-and-message-queues-e69742d855a8?gi=e965191e72d0
  48. Kafka Queuing: Kafka as a Messaging System
    https://dzone.com/articles/kafka-queuing-kafka-as-a-messaging-system
  49. Microservices – Not a free lunch!
    http://highscalability.com/blog/2014/4/8/mi­croservices-not-a-free-lunch.html
  50. Microservices, Monoliths, and NoOps
    http://blog.arungupta.me/microservices-monoliths-noops/
  51. Microservice Design Patterns
    http://blog.arungupta.me/microservice-design-patterns/
  52. Vision of a microservice revolution
    https://www.jolie-lang.org/vision.html
  53. Microservices: a definition of this new architectural term
    https://martinfowler.com/ar­ticles/microservices.html
  54. Mikroslužby
    http://voho.eu/wiki/mikrosluzba/
  55. Microservice Prerequisites
    https://martinfowler.com/bli­ki/MicroservicePrerequisi­tes.html
  56. Microservices in Practice, Part 1: Reality Check and Service Design (vyžaduje registraci)
    https://ieeexplore.ieee.or­g/document/7819415
  57. Microservice Trade-Offs
    https://www.martinfowler.com/ar­ticles/microservice-trade-offs.html
  58. What is a microservice? (from a linguistic point of view)
    http://claudioguidi.blogspot­.com/2017/03/what-microservice-from-linguisitc.html
  59. Microservices (Wikipedia)
    https://en.wikipedia.org/wi­ki/Microservices
  60. Fallacies of distributed computing (Wikipedia)
    https://en.wikipedia.org/wi­ki/Fallacies_of_distributed_com­puting
  61. Service (systems architecture)
    https://en.wikipedia.org/wi­ki/Service_(systems_archi­tecture)
  62. Microservices in a Nutshell
    https://www.thoughtworks.com/in­sights/blog/microservices-nutshell
  63. What is Microservices?
    https://smartbear.com/solu­tions/microservices/
  64. Mastering Chaos – A Netflix Guide to Microservices
    https://www.youtube.com/wat­ch?v=CZ3wIuvmHeM&t=17s
  65. Messaging in Microservice Architecture
    https://www.youtube.com/wat­ch?v=MkQWQ5f-SEY
  66. Pattern: Messaging
    https://microservices.io/pat­terns/communication-style/messaging.html
  67. Microservices Messaging: Why REST Isn’t Always the Best Choice
    https://blog.codeship.com/mi­croservices-messaging-rest-isnt-always-best-choice/
  68. Protocol buffers
    https://developers.google.com/protocol-buffers/
  69. BSON
    http://bsonspec.org/
  70. Apache Avro!
    https://avro.apache.org/
  71. REST vs Messaging for Microservices – Which One is Best?
    https://solace.com/blog/experience-awesomeness-event-driven-microservices/
  72. How did we end up here?
    https://gotocon.com/dl/goto-chicago-2015/slides/MartinThompson_an­d_ToddMontgomery_HowDidWe­EndUpHere.pdf
  73. Scaling microservices with message queues to handle data bursts
    https://read.acloud.guru/scaling-microservices-with-message-queue-2d389be5b139
  74. Microservices: What are smart endpoints and dumb pipes?
    https://stackoverflow.com/qu­estions/26616962/microser­vices-what-are-smart-endpoints-and-dumb-pipes
  75. Common Object Request Broker Architecture
    https://en.wikipedia.org/wi­ki/Common_Object_Request_Bro­ker_Architecture
  76. Enterprise service bus
    https://en.wikipedia.org/wi­ki/Enterprise_service_bus
  77. Microservices vs SOA : What’s the Difference
    https://www.edureka.co/blog/mi­croservices-vs-soa/
  78. Pravda o SOA
    https://businessworld.cz/reseni-a-realizace/pravda-o-soa-2980
  79. Is it a good idea for Microservices to share a common database?
    https://www.quora.com/Is-it-a-good-idea-for-Microservices-to-share-a-common-database
  80. Pattern: Shared database
    https://microservices.io/pat­terns/data/shared-database.html
  81. Is a Shared Database in Microservices Actually an Anti-pattern?
    https://hackernoon.com/is-shared-database-in-microservices-actually-anti-pattern-8cc2536adfe4
  82. Shared database in microservices is a problem, yep
    https://ayende.com/blog/186914-A/shared-database-in-microservices-is-a-problem-yep
  83. Microservices with shared database? using multiple ORM's?
    https://stackoverflow.com/qu­estions/43612866/microser­vices-with-shared-database-using-multiple-orms
  84. Examples of microservice architecture
    https://www.coursera.org/lecture/intro-ibm-microservices/examples-of-microservice-architecture-JXOFj
  85. Microservices: The Rise Of Kafka
    https://movio.co/blog/microservices-rise-kafka/
  86. Building a Microservices Ecosystem with Kafka Streams and KSQL
    https://www.confluent.io/blog/building-a-microservices-ecosystem-with-kafka-streams-and-ksql/
  87. An introduction to Apache Kafka and microservices communication
    https://medium.com/@ulymarins/an-introduction-to-apache-kafka-and-microservices-communication-bf0a0966d63
  88. ACID (computer science)
    https://en.wikipedia.org/wi­ki/ACID_(computer_science)
  89. Distributed transaction
    https://en.wikipedia.org/wi­ki/Distributed_transaction
  90. Two-phase commit protocol
    https://en.wikipedia.org/wiki/Two-phase_commit_protocol
  91. Why is 2-phase commit not suitable for a microservices architecture?
    https://stackoverflow.com/qu­estions/55249656/why-is-2-phase-commit-not-suitable-for-a-microservices-architecture
  92. 4 reasons why microservices resonate
    https://www.oreilly.com/ideas/4-reasons-why-microservices-resonate
  93. Pattern: Microservice Architecture
    https://microservices.io/pat­terns/microservices.html
  94. Pattern: Monolithic Architecture
    https://microservices.io/pat­terns/monolithic.html
  95. Pattern: Saga
    https://microservices.io/pat­terns/data/saga.html
  96. Pattern: Database per service
    https://microservices.io/pat­terns/data/database-per-service.html
  97. Pattern: Access token
    https://microservices.io/pat­terns/security/access-token.html
  98. Databázová integrita
    https://cs.wikipedia.org/wi­ki/Datab%C3%A1zov%C3%A1_in­tegrita
  99. Referenční integrita
    https://cs.wikipedia.org/wi­ki/Referen%C4%8Dn%C3%AD_in­tegrita
  100. Introduction into Microservices
    https://specify.io/concep­ts/microservices
  101. Are Microservices ‘SOA Done Right’?
    https://intellyx.com/2015/07/20/are-microservices-soa-done-right/
  102. The Hardest Part About Microservices: Your Data
    https://blog.christianpos­ta.com/microservices/the-hardest-part-about-microservices-data/
  103. From a monolith to microservices + REST
    https://www.slideshare.net/InfoQ/from-a-monolith-to-microservices-rest-the-evolution-of-linkedins-service-architecture
  104. DevOps and the Myth of Efficiency, Part I
    https://blog.christianpos­ta.com/devops/devops-and-the-myth-of-efficiency-part-i/
  105. DevOps and the Myth of Efficiency, Part II
    https://blog.christianpos­ta.com/devops/devops-and-the-myth-of-efficiency-part-ii/
  106. Standing on Distributed Shoulders of Giants: Farsighted Physicists of Yore Were Danged Smart!
    https://queue.acm.org/deta­il.cfm?id=2953944
  107. Building DistributedLog: High-performance replicated log service
    https://blog.twitter.com/en­gineering/en_us/topics/in­frastructure/2015/building-distributedlog-twitter-s-high-performance-replicated-log-servic.html
  108. Turning the database inside-out with Apache Samza
    https://www.confluent.io/blog/turning-the-database-inside-out-with-apache-samza/
  109. Debezium: Stream changes from your databases.
    https://debezium.io/
  110. Change data capture
    https://en.wikipedia.org/wi­ki/Change_data_capture
  111. Apache Samza (Wikipedia)
    https://en.wikipedia.org/wi­ki/Apache_Samza
  112. Storm (event processor)
    https://en.wikipedia.org/wi­ki/Storm_(event_processor)
  113. kappa-architecture.com
    http://milinda.pathirage.org/kappa-architecture.com/
  114. Questioning the Lambda Architecture
    https://www.oreilly.com/i­deas/questioning-the-lambda-architecture
  115. Lambda architecture
    https://en.wikipedia.org/wi­ki/Lambda_architecture
  116. Event stream processing
    https://en.wikipedia.org/wi­ki/Event_stream_processing
  117. How to beat the CAP theorem
    http://nathanmarz.com/blog/how-to-beat-the-cap-theorem.html
  118. Kappa Architecture Our Experience
    https://events.static.linux­found.org/sites/events/fi­les/slides/ASPgems%20-%20Kappa%20Architecture.pdf
  119. Messaging Patterns in Event Driven Microservice Architectures
    https://www.youtube.com/wat­ch?v=3×Dc4MEYuHI
  120. Why monolithic apps are often better than microservices
    https://gigaom.com/2015/11/06/why-monolithic-apps-are-often-better-than-microservices/
  121. How Enterprise PaaS Can Add Critical Value to Microservices
    https://apprenda.com/blog/enterprise-paas-microservices/
  122. Common React Mistakes: Monolithic Components and a Lack of Abstraction
    https://www.pmg.com/blog/common-react-mistakes-monolithic-components-lack-abstraction/
  123. From monolith to microservices – to migrate or not to migrate?
    https://altkomsoftware.pl/en/blog/mo­nolith-microservices/
  124. Command–query separation
    https://en.wikipedia.org/wi­ki/Command%E2%80%93query_se­paration
  125. GOTO 2016: Messaging and Microservices (Clemens Vasters)
    https://www.youtube.com/wat­ch?v=rXi5CLjIQ9kx
  126. GOTO Amsterdam 2019
    https://gotoams.nl/
  127. Lesson 2 – Kafka vs. Standard Messaging
    https://www.youtube.com/wat­ch?v=lwMjjTT1Q-Q
  128. CommandQuerySeparation (Martin Fowler)
    https://martinfowler.com/bli­ki/CommandQuerySeparation­.html
  129. Command–query separation
    https://en.wikipedia.org/wi­ki/Command%E2%80%93query_se­paration
  130. CQRS – Martin Fowler
    https://martinfowler.com/bli­ki/CQRS.html
  131. Lesson 12 – CQRS and Microservices
    https://www.youtube.com/wat­ch?v=pUGvXUBfvEE
  132. Message queues - the right way to process and work with realtime data on your servers
    https://www.ably.io/blog/message-queues-the-right-way
  133. Function as a service
    https://en.wikipedia.org/wi­ki/Function_as_a_service
  134. AWS Lambda
    https://en.wikipedia.org/wi­ki/AWS_Lambda