Created
March 22, 2019 14:27
-
-
Save haleyrc/02358a4476ecab34e0d60449e7ded551 to your computer and use it in GitHub Desktop.
A reminder that complex map keys do exist
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This gist is a reminder to don't be like me and forget that you can | |
// use complex keys for maps. | |
// | |
// https://play.golang.org/p/KCJ58WH2jU- | |
package main | |
import "fmt" | |
func main() { | |
// Before continuing to the details, confirm for yourself that | |
// the client code is identical here. | |
cid := "18292" | |
{ | |
ndb := make(naiiveDatabase) | |
id := ndb.AddVehicle( | |
cid, | |
Vehicle{Year: "2016", | |
Make: "Jeep", | |
Model: "Patriot"}, | |
) | |
fmt.Println("ID: ", id) | |
v, err := ndb.GetVehicle(cid, id) | |
if err != nil { | |
fmt.Println(err) | |
} else { | |
fmt.Printf("%#v\n", v) | |
} | |
} | |
{ | |
sdb := make(smartDatabase) | |
id := sdb.AddVehicle( | |
cid, | |
Vehicle{Year: "2016", | |
Make: "Jeep", | |
Model: "Patriot"}, | |
) | |
fmt.Println("ID: ", id) | |
v, err := sdb.GetVehicle(cid, id) | |
if err != nil { | |
fmt.Println(err) | |
} else { | |
fmt.Printf("%#v\n", v) | |
} | |
} | |
} | |
// When standing up a mock database for testing, I was naiively just putting | |
// everything into complicated series of maps to simulate a composite key of | |
// cid and entity id. This works, but it can get difficult to manage | |
// especially when entities have additional constraints. | |
type naiiveDatabase map[string]map[int]Vehicle | |
// AddVehicle is fairly straightforward, except that we first have to make | |
// sure the sub-map for the given cid exists since this isn't handled by | |
// the call to make that created the initial database. | |
func (db naiiveDatabase) AddVehicle(cid string, v Vehicle) int { | |
if _, ok := db[cid]; !ok { | |
db[cid] = make(map[int]Vehicle) | |
} | |
id := uuid.New() | |
v.ID = id | |
db[cid][id] = v | |
return id | |
} | |
// GetVehicle is even more complex because we have to do our existence check | |
// twice. What's more, this doesn't really reflect the type of thing you would | |
// see in a database lookup, since the first error corresponds to no entries | |
// with the cid, and the second corresponds to no entries with the id. In reality | |
// though, we really just want to know if there is *an* entry with the full | |
// composite key. | |
func (db naiiveDatabase) GetVehicle(cid string, id int) (Vehicle, error) { | |
sub, ok := db[cid] | |
if !ok { | |
return Vehicle{}, ErrNotFound | |
} | |
v, ok := sub[id] | |
if !ok { | |
return Vehicle{}, ErrNotFound | |
} | |
return v, nil | |
} | |
// The smarter version of this makes use of the fact that keys to maps don't have | |
// to be primitive types. We still have the top level map, but now we key into it | |
// with a composite key. | |
type smartDatabase map[vehicleKey]Vehicle | |
// Our composite key type can now match the structure of the database index more | |
// naturally and can be extended for larger composites. | |
type vehicleKey struct { | |
cid string | |
id int | |
} | |
// AddVehicle is now simpler because the top level map is all we have to worry | |
// about and that's taken care of by the initial call to make. | |
func (db smartDatabase) AddVehicle(cid string, v Vehicle) int { | |
id := uuid.New() | |
v.ID = id | |
key := vehicleKey{cid, id} | |
db[key] = v | |
return id | |
} | |
// GetVehicle is even more simplified and the error we return corresponds directly | |
// to what we would expect: the vehicle identified by the composite index (cid, id) | |
// does not exist. | |
func (db smartDatabase) GetVehicle(cid string, id int) (Vehicle, error) { | |
key := vehicleKey{cid, id} | |
v, ok := db[key] | |
if !ok { | |
return Vehicle{}, ErrNotFound | |
} | |
return v, nil | |
} | |
// Utility stuff not important to the demo | |
var ErrNotFound = fmt.Errorf("not found") | |
type Vehicle struct { | |
ID int | |
Year, Make, Model string | |
} | |
type idgen struct { | |
last int | |
} | |
func (g *idgen) New() int { | |
g.last++ | |
return g.last | |
} | |
var uuid = &idgen{} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment