• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..--

READMES/11-Mar-2024-6540

test/11-Mar-2024-3614

Makefile.amD11-Mar-20241.2 KiB4116

README.mdD11-Mar-20249.9 KiB254183

ensure-externals.shD11-Mar-2024719 1713

load-locally.lispD11-Mar-2024978 245

README.md

1Thrift Common Lisp Library
2
3License
4=======
5
6Licensed to the Apache Software Foundation (ASF) under one
7or more contributor license agreements. See the NOTICE file
8distributed with this work for additional information
9regarding copyright ownership. The ASF licenses this file
10to you under the Apache License, Version 2.0 (the
11"License"); you may not use this file except in compliance
12with the License. You may obtain a copy of the License at
13
14  http://www.apache.org/licenses/LICENSE-2.0
15
16Unless required by applicable law or agreed to in writing,
17software distributed under the License is distributed on an
18"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19KIND, either express or implied. See the License for the
20specific language governing permissions and limitations
21under the License.
22
23
24
25Using Thrift with Common Lisp
26============================
27
28 Thrift is a protocol and library for language-independent communication between cooperating
29 processes. The communication takes the form of request and response messages, of which the forms
30 are specified in advance throufh a shared interface definition. A Thrift definition file is translated
31 into Lisp source files, which comprise several definitions:
32
33  * Three packages, one for the namespace of the implementation operators, and one each for request and
34  response operators.
35  * Various type definitions as implementations for Thrift typedef and enum definitions.
36  * DEF-STRUCT and DEF-EXCEPTION forms for Thrift struct and exception definitions.
37  * DEF-SERVICE forms for thrift service definitions.
38
39 Each service definition expands in a collection of generic function definitions. For each `op`
40 in the service definition, two functions are defined
41
42  * `op`-request is defined for use by a client. It accepts an additional initial `protocol` argument,
43    to act as the client proxy for the operation and mediate the interaction with a remote process
44    through a Thrift-encoded transport stream.
45  * `op`-response is defined for use by a server. It accepts a single `protocol` argument. A server
46    uses it to decode the request message, invoke the base `op` function with the message arguments,
47    encode and send the the result as a response, and handles exceptions.
48
49 The client interface is one operator
50
51  * `with-client (variable location) . body` : creates a connection in a dynamic context and closes it
52    upon exit. The variable is bound to a client proxy stream/protocol instance, which wraps the
53    base i/o stream - socket, file, etc, with an operators which implement the Thrift protocol
54    and transport mechanisms.
55
56 The server interface combines server and service objects
57
58  * `serve (location service)` : accepts connections on the designated port and responds to
59    requests of the service's operations.
60
61
62Building
63--------
64
65The Thrift Common Lisp library is packaged as the ASDF[[1]] system `thrift`.
66It depends on the systems
67
68* puri[[2]] : for the thrift uri class
69* closer-mop[[3]] : for class metadata
70* trivial-utf-8[[4]] : for string codecs
71* usocket[[5]] : for the socket transport
72* ieee-floats[[6]] : for conversion between ints and floats
73* trivial-gray-streams[[7]] : an abstraction layer for gray streams
74* alexandria[[8]] : handy utilities
75
76The dependencies are bundled for local builds of tests and tutorial binaries -
77it is possible to use those bundles to load the library, too.
78
79In order to build it, register those systems with ASDF and evaluate:
80
81    (asdf:load-system :thrift)
82
83This will compile and load the Lisp compiler for Thrift definition files, the
84transport and protocol implementations, and the client and server interface
85functions. In order to use Thrift in an application, one must also author and/or
86load the interface definitions for the remote service.[[9]] If one is implementing a service,
87one must also define the actual functions to which Thrift is to act as the proxy
88interface. The remainder of this document follows the Thrift tutorial to illustrate how
89to perform the steps
90
91  * implement the service
92  * translate the Thrift IDL
93  * load the Lisp service interfaces
94  * run a server for the service
95  * use a client to access the service remotely
96
97Note that, if one is to implement a new service, one will also need to author the
98IDL files, as there is no facility to generate them from a service implementation.
99
100
101Implement the Service
102---------------------
103
104The tutorial comprises serveral functions: `add`, `ping`, `zip`, and `calculate`.
105Each translated IDL file generates three packages for every service. In the case of
106the tutorial file, the relevant packages are:
107
108  * tutorial.calculator
109  * tutorial.calculator-implementation
110  * tutorial.calculator-response
111
112This is to separate the request (generated), response (generated) and implementation
113(meant to be implemented by the programmer) functions for defined Thrift methods.
114
115It is suggested to work in the `tutorial-implementation` package while implementing
116the services - it imports the `common-lisp` package, while the service-specific ones
117don't (to avoid conflicts between Thrift method names and function names in `common-lisp`).
118
119    ;; define the base operations
120
121    (in-package :tutorial-implementation)
122
123    (defun tutorial.calculator-implementation:add (num1 num2)
124      (format t "~&Asked to add ~A and ~A." num1 num2)
125      (+ num1 num2))
126
127    (defun tutorial.calculator-implementation:ping ()
128      (print :ping))
129
130    (defun tutorial.calculator-implementation:zip ()
131      (print :zip))
132
133    (defun tutorial.calculator-implementation:calculate (logid task)
134      (calculate-op (work-op task) (work-num1 task) (work-num2 task)))
135
136    (defgeneric calculate-op (op arg1 arg2)
137      (:method :around (op arg1 arg2)
138        (let ((result (call-next-method)))
139          (format t "~&Asked to calculate: ~d on  ~A and ~A = ~d." op arg1 arg2 result)
140          result))
141
142      (:method ((op (eql operation.add)) arg1 arg2)
143        (+ arg1 arg2))
144      (:method ((op (eql operation.subtract)) arg1 arg2)
145        (- arg1 arg2))
146      (:method ((op (eql operation.multiply)) arg1 arg2)
147        (* arg1 arg2))
148      (:method ((op (eql operation.divide)) arg1 arg2)
149        (/ arg1 arg2)))
150
151    (defun zip () (print 'zip))
152
153
154Translate the Thrift IDL
155------------------------
156
157IDL files employ the file extension `thrift`. In this case, there are two files to translate
158  * `tutorial.thrift`
159  * `shared.thrift`
160As the former includes the latter, one uses it to generate the interfaces:
161
162    $THRIFT/bin/thrift -r --gen cl $THRIFT/tutorial/tutorial.thrift
163
164`-r` stands for recursion, while `--gen` lets one choose the language to translate to.
165
166
167Load the Lisp translated service interfaces
168-------------------------------------------
169
170The translator generates three files for each IDL file. For example `tutorial-types.lisp`,
171`tutorial-vars.lisp` and an `.asd` file that can be used to load them both and pull in
172other includes (like `shared` within the tutorial) as dependencies.
173
174
175Run a Server for the Service
176----------------------------
177
178The actual service name, as specified in the `def-service` form in `tutorial.lisp`, is `calculator`.
179Each service definition defines a global variable with the service name and binds it to a
180service instance whch describes the operations.
181
182In order to start a service, specify a location and the service instance.
183
184    (in-package :tutorial)
185    (serve #u"thrift://127.0.0.1:9091" calculator)
186
187
188Use a Client to Access the Service Remotely
189-------------------------------------------
190
191
192[in some other process] run the client
193
194    (in-package :cl-user)
195
196    (macrolet ((show (form)
197                 `(format *trace-output* "~%~s =>~{ ~s~}"
198                          ',form
199                          (multiple-value-list (ignore-errors ,form)))))
200      (with-client (protocol #u"thrift://127.0.0.1:9091")
201        (show (tutorial.calculator:ping protocol))
202        (show (tutorial.calculator:add protocol 1 2))
203        (show (tutorial.calculator:add protocol 1 4))
204
205        (let ((task (make-instance 'tutorial:work
206                      :op operation.subtract :num1 15 :num2 10)))
207          (show (tutorial.calculator:calculate protocol 1 task))
208
209          (setf (tutorial:work-op task) operation.divide
210                (tutorial:work-num1 task) 1
211                (tutorial:work-num2 task) 0)
212          (show (tutorial.calculator:calculate protocol 1 task)))
213
214        (show (shared.shared-service:get-struct protocol 1))
215
216        (show (zip protocol))))
217
218Issues
219------
220
221### optional fields
222 Where the IDL declares a field options, the def-struct form includes no
223 initform for the slot and the encoding operator skips an unbound slot. This leave some ambiguity
224 with bool fields.
225
226### instantiation protocol :
227 struct classes are standard classes and exception classes are
228 whatever the implementation prescribes. decoders apply make-struct to an initargs list.
229 particularly at the service end, there are advantages to resourcing structs and decoding
230 with direct side-effects on slot-values
231
232### maps:
233 Maps are now represented as hash tables. As data through the call/reply interface is all statically
234 typed, it is not necessary for the objects to themselves indicate the coding form. Association lists
235 would be sufficient. As the key type is arbitrary, property lists offer no additional convenience:
236 as `getf` operates with `eq` a new access interface would be necessary and they would not be
237 available for function application.
238
239
240 [1]: www.common-lisp.net/asdf
241 [2]: http://github.com/lisp/com.b9.puri.ppcre
242 [3]: www.common-lisp.net/closer-mop
243 [4]: trivial-utf-8
244 [5]: https://github.com/usocket/usocket
245 [6]: https://github.com/marijnh/ieee-floats
246 [7]: https://github.com/trivial-gray-streams/trivial-gray-streams
247 [8]: https://gitlab.common-lisp.net/alexandria/alexandria
248 [9]: http://wiki.apache.org/thrift/ThriftGeneration
249
250* usocket[[5]] : for the socket transport
251* ieee-floats[[6]] : for conversion between ints and floats
252* trivial-gray-streams[[7]] : an abstraction layer for gray streams
253* alexandria[[8]] : handy utilities
254