-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathdemo2.lisp
175 lines (150 loc) · 5.48 KB
/
demo2.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
(defpackage #:clobber-demo/demo2
(:use #:common-lisp #:clobber-demo/common)
(:export #:do-things
#:*operator*
#:*comment*
#:with-comment
#:transaction
#:function-name
#:arguments
#:creator
#:comment
#:*transactions*
#:log-to-list
#:commit-to-list
#:clear-uncommitted-to-list))
(in-package #:clobber-demo/demo2)
;;; In this demo we keep a "transaction log" which is a 1-to-1 mapping
;;; of the Clobber transaction log, but which can be used to query the
;;; system for information such as "who closed that account?" "when
;;; was it done?", etc.
;;; The person operating the system.
(defparameter *operator* "Suzy")
;;; The comment to store in the current transaction.
(defparameter *comment* "")
;;; Wrap the execution of a a transaction in this macro
;;; if a comment is desired.
(defmacro with-comment (comment &body body)
`(let ((*comment* ,comment))
,@body))
(defclass transaction ()
((%function-name :initarg :function-name :reader function-name)
(%arguments :initarg :arguments :reader arguments)
(%creator :initform *operator* :initarg :creator :reader creator)
(%creation-date :initform (get-universal-time)
:initarg :creation-date
:reader creation-date)
(%comment :initform *comment* :initarg :comment :reader comment)))
(clobber:define-save-info transaction
(:function-name function-name)
(:arguments arguments)
(:creator creator)
(:creation-date creation-date)
(:comment comment))
(defmethod print-object ((tr transaction) stream)
(progn ;;print-unreadable-object (tr stream :type nil :identity nil)
(with-accessors ((function-name function-name)
(arguments arguments)) tr
(format stream "#T(~A ~{~A~^ ~})" function-name arguments))))
;;; The transaction log mirrored as a list
(defparameter *transactions* '())
(defparameter *tmp-transactions* '())
(defun log-to-list (transaction)
(push transaction *tmp-transactions*))
(defun commit-to-list ()
(setf *transactions* (append *tmp-transactions* *transactions*))
(setf *tmp-transactions* '()))
(defun clear-uncommitted-to-list ()
(setf *tmp-transactions* '()))
(defun execute (transaction-function &rest arguments)
;; if an error happened during a previous execution
;; of some transaction,
;; remove what was logged temporarily but not committed
(clobber:clear-uncommitted *transaction-log*)
(clear-uncommitted-to-list)
(let ((transaction (make-instance 'transaction
:function-name transaction-function
:arguments arguments)))
;; log the transaction to a temporary buffer
;; before executing it, because the execution
;; may change the objects from the transaction
;; which is to be logged
;; (we want to log the arguments for the function
;; as they were before executing the function)
(clobber:log-transaction transaction
*transaction-log*)
;; also log to a temporary list that will be committed
;; to our list that mirrors the log file
(log-to-list transaction)
;; now execute the transaction
(apply transaction-function arguments)
;; if the execution was successful,
;; commit the temporary buffer to the file
(clobber:commit *transaction-log*)
;; also commit to our list that mirrors the log file
(commit-to-list)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Starting and stopping.
(defun start (filename)
(setf *banks* '())
(setf *transactions* '())
(setf *transaction-log*
(clobber:open-transaction-log
filename
(lambda (transaction)
(apply (function-name transaction)
(arguments transaction))
(push transaction *transactions*)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; The actual application
(defun do-things ()
(let ((b1 (make-instance 'bank))
(b2 (make-instance 'bank))
(jane (make-instance 'person :name "Jane"))
(bill (make-instance 'person :name "Bill")))
(execute 'new-bank b1)
(execute 'new-bank b2)
(execute 'add-customer jane b1)
(with-comment "What does bank 2 have that bank 1 does not?"
(execute 'add-customer jane b2))
(execute 'add-customer bill b1)
(let ((a1 (make-instance 'account :holder jane))
(a2 (make-instance 'account :holder jane))
(a3 (make-instance 'account :holder bill)))
(execute 'add-account a1 b1)
(execute 'add-account a2 b2)
(execute 'add-account a3 b1)
(with-comment "Gee, I wish they would deposit more money!"
(execute 'deposit 100 a1))
(execute 'deposit 200 a2)
(execute 'deposit 300 a3)
(execute 'withdraw 10 a3)
(execute 'transfer 20 a2 a1))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;
;;; Demonstrating the application
;;;
;;; (delete-database) ; clean up
;;; (do-and-see) ; see what the database file contains after the execution of transactions
;;; (reload-database) ; see that *banks* has the data freshly revived from the database file.
(defvar *database-file* (merge-pathnames "demo2-database" (user-homedir-pathname)))
(defun do-and-see ()
(start *database-file*)
(do-things)
;;; inspect the file to see the transaction log
(with-open-file (stream *database-file*)
(let ((data (make-string (file-length stream))))
(read-sequence data stream)
data)))
(defun reload-database ()
(stop)
(start *database-file*))
(defun reload-database-and-see ()
(reload-database)
*banks*)
(defun delete-database ()
(stop)
(when (probe-file *database-file*)
(delete-file *database-file*)))