Kaip sukurti sandorio pagrindinės vertės parduotuvę „Go“

Jei norite sukurti interaktyvų apvalkalą, leidžiantį pasiekti operacijų atminties raktų / vertybių saugyklą, būsite tinkamoje vietoje.

Eikime kartu ir suprojektuokime vieną dabar.

Backstory

Sistemos projektavimo klausimai mane visada domino, nes jie leidžia būti kūrybingiems.

Neseniai skaičiau Uduako tinklaraštį, kuriame jis pasidalijo savo patirtimi atlikdamas 30 dienų interviu maratoną, kuris buvo gana įdomus. Aš labai rekomenduoju jį perskaityti.

Šiaip ar taip, aš sužinojau apie šį įdomų sistemos projektavimo klausimą, kurį jam uždavė interviu metu.

Iššūkis

Klausimas yra toks:

Sukurkite interaktyvų apvalkalą, kuris suteikia prieigą prie „operacinio atminties rakto / vertės saugyklos“.

Pastaba : klausimas yra performuluotas, kad būtų lengviau suprasti. Jis buvo pateiktas kaip „namo parsivežimo“ projektas minėto autoriaus interviu metu.

Korpusas turėtų priimti šias komandas:

Komanda apibūdinimas
SET Nustato nurodytą raktą į nurodytą vertę. Raktą taip pat galima atnaujinti.
GET Atspausdina dabartinę nurodyto rakto vertę.
DELETE Ištrina pateiktą raktą. Jei raktas nebuvo nustatytas, nepaisykite.
COUNT Grąžina nustatytų raktų skaičių. Jei ta reikšmė nenustatyta, spausdinama 0.
BEGIN Pradeda operaciją. Šios operacijos leidžia modifikuoti sistemos būseną ir atlikti arba grąžinti pakeitimus.
END Baigia operaciją. Prarandama viskas, kas padaryta „aktyvios“ operacijos metu.
ROLLBACK Išmeta pakeitimus, padarytus aktyvios operacijos kontekste. Jei nė viena operacija neaktyvi, spausdinama „Nėra aktyvios operacijos“.
COMMIT Atlieka pakeitimus, padarytus aktyvios operacijos kontekste, ir nutraukia aktyvią operaciją.

Mes arenoje?

Prieš pradėdami galime užduoti keletą papildomų klausimų, tokių kaip:

Q1. Ar duomenys išlieka pasibaigus interaktyviam apvalkalo seansui?

Q2. Ar operacijos su duomenimis atspindi pasaulinį apvalkalą?

Q3. Ar įpareigoti įdėto sandorio pokyčius atspindi ir seneliai?

Jūsų klausimai gali skirtis, o tai yra tobula. Kuo daugiau klausimų užduosite, tuo geriau suprasite problemą.

Problemos sprendimas daugiausia priklausys nuo užduotų klausimų, todėl apibrėžkime, ko prisiimsime kurdami savo pagrindinės vertės parduotuvę:

  1. Duomenys nėra nuolatiniai (tai yra, kai tik baigiasi apvalkalo sesija, duomenys prarandami).
  2. Pagrindinės reikšmės gali būti tik eilutės (galime pritaikyti sąsajas pasirinktiniams duomenų tipams, tačiau tai nėra šios mokymo programos taikymo sritis).

Dabar pabandykime suprasti keblią mūsų problemos dalį.

„Operacijos“ supratimas

Operacija sukuriama naudojant BEGINkomandą ir sukuria kontekstą kitoms operacijoms įvykti. Pavyzdžiui:

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

Tai yra dabartinė aktyvi operacija ir visos operacijos veikia tik jos viduje.

Kol aktyvi operacija nebus vykdoma naudojant COMMITkomandą, šios operacijos neišlieka. Ir, The ROLLBACKkomandą išmeta bet kokius pakeitimus, padarytus naudojant tas operacijas, kurios veikliosios sandorio kontekste. Tiksliau sakant, iš žemėlapio ištrinamos visos raktų ir verčių poros.

Pavyzdžiui:

> 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 

Sandoris taip pat gali būti įdėtas, ty turėkite ir antrinių operacijų:

Naujai pagimdytas sandoris paveldi kintamuosius iš pagrindinio sandorio, o pakeitimai, atlikti atliekant antrinę operaciją, atsispindės ir pirminiame sandoryje.

Pavyzdžiui:

> 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** 

Aš daviau jai kadrą tik perskaičiusi tinklaraštį. Pažiūrėkime, kaip mes galime tai išspręsti.

Kurkime

Aptarėme, kad sandoriai taip pat gali turėti antrines operacijas, tam apibendrinti galime naudoti duomenų kaupinio struktūrą:

  • Kiekvienas kamino elementas yra operacija .
  • Viršutinėje dalyje saugoma dabartinė „aktyvi“ operacija.
  • Kiekvienas operacijos elementas turi savo žemėlapį. Vadinsime tai „vietine parduotuve“, kuri veikia kaip vietinė talpykla - kiekvieną kartą, SETkai sandorio kintamasis, ši parduotuvė yra atnaujinama.
  • Kai pakeitimai bus įpareigoti operacijos metu, šios „vietinės“ parduotuvės vertės bus įrašytos į mūsų visuotinio žemėlapio objektą.

Mes naudosime „Linked list“ kamino įgyvendinimą. Tai taip pat galime pasiekti naudodami dinaminius masyvus, tačiau tai yra skaitytojo namų darbas:

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. } 
  • Mūsų kaminą vaizduoja struktūra, TransactionStackkurioje saugomas tik rodyklės toprodinys. sizeyra struktūrinis kintamasis, kuris gali būti naudojamas nustatant mūsų kamino dydį, ty norint rasti sustabdytų ir aktyvių operacijų skaičių (visiškai neprivaloma - galite to nedeklaruoti).
  • TransactionStruct turi parduotuvę kurioje mes apibrėžtą anksčiau kaip žemėlapio ir rodyklę į kitą sandorio atmintyje.
  • GlobalStore is a map which is shared by all the transactions in the stack. This is how we achieve a parent-child relationship, but more on this later.

Now let's write the push and pop methods for our 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-- } } 
  • With every BEGIN operation, a new stack element is pushed into the TransactionStack and updates top to this value.
  • For every COMMIT or END operation, the active transaction is popped from the stack and the next element of the stack is assigned to top. Hence the parent transaction is now our current active transaction.

If you are new to Go, note that PushTransaction() and PopTransaction() are methods and not functions of receiver type (*TransactionStack).

In languages like JavaScript and Python, the receiver method invocation is achieved by the keywords this and self, respectively.

However in Go this is not the case. You can name it anything you want. To make it easier to understand we choose ts to refer to the transaction stack.

Now we create a Peek method to return us the top element from the stack:

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

Note that we are returning a pointer variable of type Transaction.

COMMITing a transaction will involve "copying" all the new and/or updated values from the transaction local store to our 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 } 

Rolling back a transaction is pretty easy. Just delete all the keys from the map (the local map of a transaction):

/*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) } } } 

And finally, here are the GET and SET functions:

/*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) } } } 

While SETing a variable, we also have to consider the case when the user might not run any transactions at all. This means that our stack will be empty, that is, the user is SETing variables in the global shell itself.

> SET F 55 > GET F 55 

In this case we can directly update our 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 } } 

Are you still with me? Don't go!

mes dabar esame galutiniame žaidime

We are pretty much done with our key-value store, so let's write the driver code:

 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]) } } } 

The COUNT and DELETE operations are fairly easy to implement if you stuck with me until now.

I encourage you to do this as homework, but I have provided my implementation below if you get stuck somewhere.

Time for testing ⚔.

zoe-demo

And let me leave you with my source code - you can give the repo a star if you want to support my work.

If you liked this tutorial, you can read more of my stuff at my blog.

Turite abejonių, kažkas negerai, ar turite atsiliepimų? Susisiekite su manimi „Twitter“ arba tiesiogiai atsiųskite man el. Laišką.

„MariaLetta“ „Gophers“ / „free-gophers-pack“

Laimingo mokymosi?