Build a Commit Log Prototype

We’ll explore commit logs in depth in Chapter 3, Write a Log Package, when we build a persisted commit log library. For now, all you need to know about commit logs is that they’re a data structure for an append-only sequence of records, ordered by time, and you can build a simple commit log with a slice.

Create an internal/server directory tree in the root of your project and put the following code under the server directory in a file called log.go:

LetsGo/internal/server/log.go
 package​ server
 
 import​ (
 "fmt"
 "sync"
 )
 
 type​ Log ​struct​ {
  mu sync.Mutex
  records []Record
 }
 
 func​ NewLog() *Log {
 return​ &Log{}
 }
 
 func​ (c *Log) Append(record Record) (​uint64​, ​error​) {
  c.mu.Lock()
 defer​ c.mu.Unlock()
  record.Offset = ​uint64​(len(c.records))
  c.records = append(c.records, record)
 return​ record.Offset, nil
 }
 
 func​ (c *Log) Read(offset ​uint64​) (Record, ​error​) {
  c.mu.Lock()
 defer​ c.mu.Unlock()
 if​ offset >= ​uint64​(len(c.records)) {
 return​ Record{}, ErrOffsetNotFound
  }
 return​ c.records[offset], nil
 }
 
 type​ Record ​struct​ {
  Value []​byte​ ​`json:"value"`
  Offset ​uint64​ ​`json:"offset"`
 }
 
 
 var​ ErrOffsetNotFound = fmt.Errorf(​"offset not found"​)

To append a record to the log, you just append to the slice. Each time we read a record given an index, we use that index to look up the record in the slice. If the offset given by the client doesn’t exist, we return an error saying that the offset doesn’t exist. All really simple stuff, as it should be since we’re using this log as a prototype and want to keep moving.

Ignore Chapter Namespaces in the File Paths

images/aside-icons/important.png

You may have noticed that code snippet’s file path said LetsGo/internal/server/log.go instead of internal/server/log.go and that subsequent code snippets have similar per-chapter directory namespaces. These namespaces were needed to structure the code for the book build. When writing your code, pretend that these namespaces don’t exist. So for the previous example, the internal directory would go at the root of your project.