Kulcs-érték tár REST API-val: kvault

A werkstatt sorozat nyolcadik része – egy key-value store ami JSON fájlba perzisztál és HTTP-n keresztül kezelhető.

Kulcs-érték tár REST API-val: kvault

Néha kell egy egyszerű kulcs-érték tár. Nem Redis, nem PostgreSQL – csak egy hely ahol egy API kulcsot, egy konfigurációs értéket, vagy egy ideiglenes adatot el tudsz rakni és HTTP-n eléred.

A kvault pontosan ennyi. Egy JSON fájlban tárolja az adatot, és REST API-n keresztül kezelhető: GET, POST, DELETE. Induláskor visszatölti a fájlt a memóriába, minden írásnál elmenti.

CRUD egy handler-ben

Az eddigi projektek végpontjai mind egyetlen HTTP metódust kezeltek. A kvault /keys/{key} végpontja háromfélét: GET olvas, POST ír, DELETE töröl. A switch r.Method elágazás dönti el mit csináljon:

switch r.Method {
case http.MethodGet:
    // olvasás
case http.MethodPost:
    // írás
case http.MethodDelete:
    // törlés
default:
    // 405
}

Ez a REST minta alapja – ugyanaz az URL, különböző metódusok, különböző viselkedés. Az envcheck-ben flag-ek voltak, a deployer-ben subcommand-ok, a kvault-ban HTTP metódusok. Háromféle interfész, de a gondolkodásmód ugyanaz: a bemenet határozza meg a műveletet.

RWMutex

Az eredeti verzió globális map-et használt zárolás nélkül. Egy felhasználónál ez működik, de ha két kérés egyszerre ír, a Go race detector hibát jelez.

A production ready verzió sync.RWMutex-et használ:

func (s *fileStore) get(key string) (string, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    v, ok := s.data[key]
    return v, ok
}

Az RLock olvasási zárolás – több goroutine egyszerre olvashat. A Lock írási zárolás – kizárólagos, sem olvasás sem másik írás nem történhet közben. Ez finomabb mint a netmapper sima Mutex-e, mert az olvasásokat nem lassítja.

A storage interface

A teszteléshez egy storage interface absztrahálja az adatkezelést:

type storage interface {
    get(key string) (string, bool)
    set(key, value string)
    del(key string) bool
    all() map[string]string
    count() int
}

A valódi fileStore JSON fájlba ment. A teszt memStore csak memóriában tartja az adatot. A handler-ek a storage interface-t kapják – nem tudják és nem is érdekli őket hogy mi van mögötte.

Ez már a negyedik projekt ahol interface-t használunk teszteléshez: deployer (runner), sshping (dialer), hookrelay (logger), kvault (storage). A minta ugyanaz, az absztrakció más.

Fájlperzisztencia tesztelés

A fileStore tesztje két store-t hoz létre ugyanarra a temp fájlra:

s1 := newFileStore(path)
s1.set("key1", "value1")

s2 := newFileStore(path)
v, _ := s2.get("key1") // "value1"

Az első store ír, a második olvas – és megtalálja amit az első mentett. Ez bizonyítja hogy az adat ténylegesen a fájlba került, nem csak a memóriában volt.

Mit tanultam?

A kvault két Go koncepciót hozott ami az eddigi projektekben nem volt:

Az RWMutex – olvasási és írási zárolás szétválasztása. Olvasni többen is tudnak egyszerre, írni csak egy. Ez a valódi szerver alkalmazások alapja.

A teljes CRUD ciklus egyetlen handler-ben – hogyan kezeld a különböző HTTP metódusokat, hogyan adj vissza megfelelő státusz kódokat (200, 201, 400, 404, 405), és hogyan tartsd konzisztensnek az API-t.

Hogyan próbáld ki?

git clone https://github.com/brtkcs/werkstatt-tools.git
cd werkstatt-tools/kvault
go run main.go

Egy másik terminálból:

curl -X POST localhost:8080/keys/secret -d '{"value":"mypassword"}'
curl localhost:8080/keys/secret
curl localhost:8080/keys
curl -X DELETE localhost:8080/keys/secret

A teljes forráskód a werkstatt-tools repóban.

Ez a werkstatt sorozat nyolcadik része. A hookrelay webhook-okat fogadott és logolt. A kvault a másik irány – CRUD műveletek HTTP-n, adatperzisztencia fájlba, és a Go sync.RWMutex a párhuzamos hozzáféréshez.