Nice article! This is an interesting approach, much less likely to make Go devs' blood boil over unnecessary libraries.
My only question is why the server / HTTP handlers have to deal with the Mutex. That seems like a "leak" from the `TaskStore` abstraction, which otherwise I really like. (Thank you for not using channels in that interface!)
I think it's necessary to leak the details of the mutex until you have some sort of transaction object to abstract that away. In a concurrent workload, these two things are different:
store.Lock()
store.WriteKey("foo", "bar")
x := store.ReadKey("foo")
store.Unlock()
// x is always "bar"
And:
store.Lock()
store.WriteKey("foo", "bar")
store.Unlock()
store.Lock()
x := store.ReadKey("foo")
store.Unlock()
// x could be whatever another goroutine set "foo" to, not the "bar" that you just wrote.
In a more complicated app, you'll have library that acts as the datastore, with transaction objects that abstract away the actual mutex (which will be something more complicated):
var x string
err := db.DoTx(func(tx *Tx) {
tx.Write("foo", "bar")
x = tx.Read("foo")
})
if err != nil { ... }
// what x is depends on the details of your database; maybe you're running at "read uncommitted", maybe you're running at "serializable".
But, even in the simple examples, it's worth thinking about the difference between lock { write; read } and lock { write }; lock { read }.
My only question is why the server / HTTP handlers have to deal with the Mutex. That seems like a "leak" from the `TaskStore` abstraction, which otherwise I really like. (Thank you for not using channels in that interface!)