Journal, the kitchen sink

Tags: Lisp, Blog

2020-09-04 -- Ever wished for machine-readable logs and TRACEs, maybe for writing tests or something more fancy? The Journal library takes a simple idea: user-defined execution traces, and implements logging, tracing, a testing "framework" with mock support, and an Event Sourcing style database on top.

Perhaps the highlight from the linked tutorials is how easy persistence is. Let's write a simple game:

(defun play-guess-my-number ()
  (let ((my-number (random 10)))
    (format t "~%I thought of a number.~%")
    (loop for i upfrom 0 do
      (write-line "Guess my number:")
      (let ((guess (values (parse-integer (read-line)))))
        (format t "You guessed ~D.~%" guess)
        (when (= guess my-number)
          (format t "You guessed it in ~D tries!" (1+ i))
          (return))))))

That came out pretty ugly, didn't it? Now, suppose we want to turn this into a browser game so:

So these are the requirements apart from adding the webby stuff, which I'm not going to bore you with. To implement them, we wrap REPLAYED around external interactions, RANDOM and READ-LINE:

(defun play-guess-my-number ()
  (let ((my-number (replayed (think-of-a-number) ; <- HERE
                     (random 10))))
    (format t "~%I thought of a number.~%")
    (loop for i upfrom 0 do
      (write-line "Guess my number:")
      (let ((guess (replayed (read-guess)        ; <- HERE
                     (values (parse-integer (read-line))))))
        (format t "You guessed ~D.~%" guess)
        (when (= guess my-number)
          (format t "You guessed it in ~D tries!" (1+ i))
          (return))))))

Now, we need to say where to store the event journal:

(with-bundle ((make-file-bundle "/tmp/guess-my-number/" :sync t))
  (play-guess-my-number))

This is invoked from the web server's worker and it replays the game until it was interrupted last time around. Then it will block waiting for user input in READ-LINE or, if the game is finished, return.

Note the :SYNC T, which tells Journal to take durability seriously.

You can find the code here.