Kuidas Go-s tehingulise võtmeväärtusega kauplust kujundada

Kui soovite kujundada interaktiivse kesta, mis võimaldab juurdepääsu tehingusisestele mäluvõtmete / väärtuste poodidele, siis olete õiges kohas.

Lähme koos ja kujundame ühe kohe.

Lugu

Süsteemikujunduse küsimused on mind alati huvitanud, sest need võimaldavad teil olla loovad.

Hiljuti lugesin Uduaki ajaveebi, kus ta jagas oma kogemusi 30-päevase intervjuumaratoni läbimisega, mis oli päris põnev. Soovitan seda väga lugeda.

Igatahes sain teada selle huvitava süsteemi kujundamise küsimuse kohta, mille talle intervjuu käigus esitati.

Väljakutse

Küsimus on järgmine:

Ehitage interaktiivne kest, mis võimaldab juurdepääsu "tehingusisestele mäluvõtmetele / väärtuste salvestamiseks".

Märkus . Parema mõistmise huvides sõnastatakse küsimus ümber. See anti ülalnimetatud autori intervjuu ajal "koju kaasa" projektina.

Kest peaks aktsepteerima järgmisi käske:

Käsk Kirjeldus
SET Määrab antud võtme määratud väärtuseks. Võti saab ka uuendada.
GET Prindib määratud võtme praeguse väärtuse.
DELETE Kustutab antud võtme. Kui võtit pole määratud, ignoreerige seda.
COUNT Tagastab määratud võtmete arvu. Kui sellele väärtusele pole määratud ühtegi võtit, prindib 0.
BEGIN Alustab tehingut. Need tehingud võimaldavad teil muuta süsteemi olekut ja teha muudatusi või neid tagasi tuua.
END Lõpeb tehingu. Kõik "aktiivse" tehingu raames tehtav on kadunud.
ROLLBACK Viskab aktiivse tehingu kontekstis tehtud muudatused minema. Kui ükski tehing pole aktiivne, prinditakse tekst „Ei aktiivset tehingut”.
COMMIT Seob aktiivse tehingu kontekstis tehtud muudatused ja lõpetab aktiivse tehingu.

Oleme areenil?

Enne alustamist võime küsida lisaküsimusi, näiteks:

Q1. Kas andmed püsivad pärast interaktiivse kestaseansi lõppu?

Q2. Kas andmetega tehtavad toimingud kajastuvad globaalses kestas?

Q3. Kas sisestatud tehingu muudatuste sidumine kajastub ka vanavanematele?

Teie küsimused võivad erineda, mis on täiuslik. Mida rohkem küsimusi esitate, seda paremini saate probleemist aru.

Probleemi lahendamine sõltub paljuski esitatavatest küsimustest, nii et määratlegem, mida me oma põhiväärtuste poe ehitamisel eeldame:

  1. Andmed on mittepüsivad (see tähendab, et kohe, kui shelliseanss lõpeb, andmed kaovad).
  2. Võtmeväärtused võivad olla ainult stringid (saame liideseid rakendada kohandatud andmetüüpide jaoks, kuid see ei kuulu selle õpetuse reguleerimisalasse).

Proovime nüüd mõista meie probleemi keerukat osa.

Tehingu mõistmine

Tehing luuakse BEGINkäsuga ja loob konteksti teiste toimingute toimumiseks. Näiteks:

> BEGIN // Creates a new transaction > SET X 200 > SET Y 14 > GET Y 14 

See on praegune aktiivne tehing ja kõik toimingud toimivad ainult selle sees.

Kuni aktiivne tehing pole COMMITkäsu abil määratud , ei toimu neid toiminguid. Ja ROLLBACKkäsk viskab need toimingud aktiivse tehingu kontekstis tehtud muudatused ära. Täpsemalt öeldes kustutab see kaardilt kõik võtmeväärtuste paarid.

Näiteks:

> BEGIN //Creates a new transaction which is currently active > SET Y 2020 > GET Y 2020 > ROLLBACK //Throws away any changes made > GET Y Y not set // Changes made by SET Y have been discarded 

Tehingu saab ka omavahel pesitseda, st teha ka alamtehinguid:

Äsja sündinud tehing pärib muutujad oma põhitehingust ja lapstehingu kontekstis tehtud muudatused kajastuvad ka vanematehingus.

Näiteks:

> BEGIN //Creates a new active transaction > SET X 5 > SET Y 19 > BEGIN //Spawns a new transaction in the context of the previous transaction and now this is currently active > GET Y Y = 19 //The new transaction inherits the context of its parent transaction** > SET Y 23 > COMMIT //Y's new value has been persisted to the key-value store** > GET Y Y = 23 // Changes made by SET Y 19 have been discarded** 

Andsin sellele pildi kohe pärast blogi lugemist. Vaatame, kuidas saame selle lahendada.

Kujundame

Arutasime, et tehingutel võib olla ka alamtehinguid, selle üldistamiseks võime kasutada virna andmestruktuuri:

  • Iga virnaelement on tehing .
  • Virna ülaosas on meie praegune aktiivne tehing.
  • Igal tehinguelemendil on oma kaart. Nimetame seda "kohalikuks poodiks", mis toimib nagu kohalik vahemälu - iga kord, kui me SETtehingu sees muutujat uuendame, seda poodi värskendatakse.
  • Kui muudatused on tehingu sisestatud, kirjutatakse selle "kohaliku" poe väärtused meie globaalsele kaardiobjektile.

Kasutame virna rakendust Linked-list. Selle võime saavutada ka dünaamiliste massiivide abil, kuid see on lugeja kodutöö:

package main import ( "fmt" "os" "bufio" "strings" ) /*GlobalStore holds the (global) variables*/ var GlobalStore = make(map[string]string) /*Transaction points to a key:value store*/ type Transaction struct { store map[string]string // every transaction has its own local store next *Transaction } /*TransactionStack maintains a list of active/suspended transactions */ type TransactionStack struct { top *Transaction size int // more meta data can be saved like Stack limit etc. } 
  • Meie korstnat tähistab struktuur, TransactionStackmis salvestab ainult topvirna kohale suunatud osuti . sizeon struktuurimuutuja, mida saab kasutada meie virna suuruse määramiseks, st peatatud ja aktiivsete tehingute arvu leidmiseks (täiesti valikuline - võite selle deklareerimata jätta).
  • Struktuuril Transactionon pood, mille määratlesime varem kaardina ja osundajana järgmisele tehingule mälus.
  • GlobalStoreon kaart, mida jagavad kõik virnas olevad tehingud. Nii saavutame vanema ja lapse suhte, kuid sellest pikemalt hiljem.

Nüüd kirjutame meie jaoks push ja pop meetodid TransactionStack.

 /*PushTransaction creates a new active transaction*/ func (ts *TransactionStack) PushTransaction() { // Push a new Transaction, this is the current active transaction temp := Transaction{store : make(map[string]string)} temp.next = ts.top ts.top = &temp ts.size++ } /*PopTransaction deletes a transaction from stack*/ func (ts *TransactionStack) PopTransaction() { // Pop the Transaction from stack, no longer active if ts.top == nil { // basically stack underflow fmt.Printf("ERROR: No Active Transactions\n") } else { node := &Transaction{} ts.top = ts.top.next node.next = nil ts.size-- } } 
  • Iga BEGINtoimingu korral lükatakse selle väärtuse juurde uus virnaelement TransactionStackja värskendatakse topseda.
  • Iga COMMITvõi ENDtoimingu jaoks hüpatakse virnast aktiivne tehing ja määratakse virna järgmine element top. Seega on vanematehing nüüd meie praegune aktiivne tehing.

Kui te olete uus Go, teadmiseks, et PushTransaction()ja PopTransaction()on meetodeid ja ei funktsioonid vastuvõtja tüüp ( *TransactionStack).

Sellistes keeltes nagu JavaScript ja Python saavutatakse vastuvõtja meetodi kutsumine vastavalt märksõnade thisja self.

Go-s pole see aga nii. Võite nimetada seda ükskõik, mida soovite. Mõistmise hõlbustamiseks otsustame tsviidata tehingupakile.

Nüüd loome Peekmeetodi topelemendi tagastamiseks virnast:

/*Peek returns the active transaction*/ func (ts *TransactionStack) Peek() *Transaction { return ts.top } 

Pange tähele, et tagastame osuti tüüpi muutuja Transaction.

Tehingu tegemine hõlmab kõigi uute ja / või värskendatud väärtuste kopeerimist tehingu kohalikust poest meie GlobalStore:

/*Commit write(SET) changes to the store with TranscationStack scope Also write changes to disk/file, if data needs to persist after the shell closes */ func (ts *TransactionStack) Commit() { ActiveTransaction := ts.Peek() if ActiveTransaction != nil { for key, value := range ActiveTransaction.store { GlobalStore[key] = value if ActiveTransaction.next != nil { // update the parent transaction ActiveTransaction.next.store[key] = value } } } else { fmt.Printf("INFO: Nothing to commit\n") } // write data to file to make it persist to disk // Tip: serialize map data to JSON } 

Tehingu tagasivõtmine on üsna lihtne. Kustutage kaardilt lihtsalt kõik võtmed (tehingu kohalik kaart):

/*RollBackTransaction clears all keys SET within a transaction*/ func (ts *TransactionStack) RollBackTransaction() { if ts.top == nil { fmt.Printf("ERROR: No Active Transaction\n") } else { for key := range ts.top.store { delete(ts.top.store, key) } } } 

Ja lõpuks, siin on GETja SETfunktsioonid:

/*Get value of key from Store*/ func Get(key string, T *TransactionStack) { ActiveTransaction := T.Peek() if ActiveTransaction == nil { if val, ok := GlobalStore[key]; ok { fmt.Printf("%s\n", val) } else { fmt.Printf("%s not set\n", key) } } else { if val, ok := ActiveTransaction.store[key]; ok { fmt.Printf("%s\n", val) } else { fmt.Printf("%s not set\n", key) } } } 

Muutuja määramisel peame arvestama ka juhtumiga, kui kasutaja ei pruugi üldse ühtegi tehingut käivitada. See tähendab, et meie virn jääb tühjaks, see tähendab, et kasutaja määrab muutujaid globaalses kestas endas.

> SET F 55 > GET F 55 

Sel juhul saame otse uuendada oma GlobalStore:

/*Set key to value */ func Set(key string, value string, T *TransactionStack) { // Get key:value store from active transaction ActiveTransaction := T.Peek() if ActiveTransaction == nil { GlobalStore[key] = value } else { ActiveTransaction.store[key] = value } } 

Kas sa oled ikka minuga? Ära mine!

oleme nüüd lõppmängus

Oleme oma võtmeväärtuste salvestusega üsna valmis, nii et kirjutame draiveri koodi:

 func main(){ reader := bufio.NewReader(os.Stdin) items := &TransactionStack{} for { fmt.Printf("> ") text, _ := reader.ReadString('\n') // split the text into operation strings operation := strings.Fields(text) switch operation[0] { case "BEGIN": items.PushTransaction() case "ROLLBACK": items.RollBackTransaction() case "COMMIT": items.Commit(); items.PopTransaction() case "END": items.PopTransaction() case "SET": Set(operation[1], operation[2], items) case "GET": Get(operation[1], items) case "DELETE": Delete(operation[1], items) case "COUNT": Count(operation[1], items) case "STOP": os.Exit(0) default: fmt.Printf("ERROR: Unrecognised Operation %s\n", operation[0]) } } } 

COUNTJa DELETEoperatsioonid on üsna lihtne rakendada, kui te ummikus mind siiani.

Soovitan teil seda teha kodutööna, kuid olen allpool esitanud oma rakenduse, kui jääte kuhugi kinni.

Aeg testimiseks ⚔.

zoe-demo

Ja las ma jätan teile oma lähtekoodi - võite anda repole tähe, kui soovite minu tööd toetada.

Kui teile see õpetus meeldis, saate minu blogist lugeda rohkem minu asju.

Kas teil on kahtlusi, midagi on valesti või on teil tagasisidet? Võtke minuga ühendust Twitteris või saatke need otse mulle.

MariaLetta goferid / free-gophers-pack

Õnnelik õppimine?