-
Notifications
You must be signed in to change notification settings - Fork 0
/
10_introduction.html
287 lines (287 loc) · 40.1 KB
/
10_introduction.html
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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
<!DOCTYPE html PUBLIC ""
"">
<html><head><meta charset="UTF-8" /><title>Introduction</title><link rel="stylesheet" type="text/css" href="css/default.css" /><link rel="stylesheet" type="text/css" href="css/highlight.css" /><script type="text/javascript" src="js/highlight.min.js"></script><script type="text/javascript" src="js/jquery.min.js"></script><script type="text/javascript" src="js/page_effects.js"></script><script>hljs.initHighlightingOnLoad();</script><link rel="stylesheet" type="text/css" href="css/randomseed.css" /></head><body><div id="header"><h2>Generated by <a href="https://github.com/weavejester/codox">Codox</a></h2><h1><a href="index.html"><span class="project-title"><span class="project-name">Amelinium</span> <span class="project-version">1.0.1</span></span></a></h1></div><div class="sidebar primary"><h3 class="no-link"><span class="inner">Project</span></h3><ul class="index-link"><li class="depth-1 "><a href="index.html"><div class="inner">Index</div></a></li></ul><h3 class="no-link"><span class="inner">Topics</span></h3><ul><li class="depth-1 current"><a href="10_introduction.html"><div class="inner"><span>Introduction</span></div></a></li><li class="depth-1 "><a href="20_hypermedia.html"><div class="inner"><span>Hypermedia-driven</span></div></a></li></ul><h3 class="no-link"><span class="inner">Namespaces</span></h3><ul><li class="depth-1"><a href="amelinium.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>amelinium</span></div></a></li><li class="depth-2 branch"><a href="amelinium.admin.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>admin</span></div></a></li><li class="depth-2"><a href="amelinium.api.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>api</span></div></a></li><li class="depth-3"><a href="amelinium.api.controller.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>controller</span></div></a></li><li class="depth-4"><a href="amelinium.api.controller.user.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>user</span></div></a></li><li class="depth-3"><a href="amelinium.api.url.html"><div class="inner"><span class="tree" style="top: -52px;"><span class="top" style="height: 61px;"></span><span class="bottom"></span></span><span>url</span></div></a></li><li class="depth-2 branch"><a href="amelinium.app.html"><div class="inner"><span class="tree" style="top: -114px;"><span class="top" style="height: 123px;"></span><span class="bottom"></span></span><span>app</span></div></a></li><li class="depth-2"><a href="amelinium.auth.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>auth</span></div></a></li><li class="depth-3"><div class="no-link"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>algo</span></div></div></li><li class="depth-4 branch"><a href="amelinium.auth.algo.append.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>append</span></div></a></li><li class="depth-4 branch"><a href="amelinium.auth.algo.fail.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>fail</span></div></a></li><li class="depth-4 branch"><a href="amelinium.auth.algo.pbkdf2.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>pbkdf2</span></div></a></li><li class="depth-4"><a href="amelinium.auth.algo.scrypt.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>scrypt</span></div></a></li><li class="depth-3 branch"><a href="amelinium.auth.pwd.html"><div class="inner"><span class="tree" style="top: -145px;"><span class="top" style="height: 154px;"></span><span class="bottom"></span></span><span>pwd</span></div></a></li><li class="depth-3"><a href="amelinium.auth.specs.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>specs</span></div></a></li><li class="depth-2"><a href="amelinium.common.html"><div class="inner"><span class="tree" style="top: -238px;"><span class="top" style="height: 247px;"></span><span class="bottom"></span></span><span>common</span></div></a></li><li class="depth-3 branch"><a href="amelinium.common.controller.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>controller</span></div></a></li><li class="depth-3"><a href="amelinium.common.oplog.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>oplog</span></div></a></li><li class="depth-4"><a href="amelinium.common.oplog.auth.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>auth</span></div></a></li><li class="depth-3 branch"><a href="amelinium.common.populators.html"><div class="inner"><span class="tree" style="top: -52px;"><span class="top" style="height: 61px;"></span><span class="bottom"></span></span><span>populators</span></div></a></li><li class="depth-3"><a href="amelinium.common.swagger.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>swagger</span></div></a></li><li class="depth-2 branch"><a href="amelinium.core.html"><div class="inner"><span class="tree" style="top: -176px;"><span class="top" style="height: 185px;"></span><span class="bottom"></span></span><span>core</span></div></a></li><li class="depth-2"><a href="amelinium.db.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>db</span></div></a></li><li class="depth-3"><a href="amelinium.db.sql.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>sql</span></div></a></li><li class="depth-2 branch"><a href="amelinium.errors.html"><div class="inner"><span class="tree" style="top: -52px;"><span class="top" style="height: 61px;"></span><span class="bottom"></span></span><span>errors</span></div></a></li><li class="depth-2"><a href="amelinium.http.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>http</span></div></a></li><li class="depth-3"><div class="no-link"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>client</span></div></div></li><li class="depth-4"><a href="amelinium.http.client.twilio.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>twilio</span></div></a></li><li class="depth-3 branch"><a href="amelinium.http.handler.html"><div class="inner"><span class="tree" style="top: -52px;"><span class="top" style="height: 61px;"></span><span class="bottom"></span></span><span>handler</span></div></a></li><li class="depth-3"><a href="amelinium.http.middleware.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>middleware</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.coercion.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>coercion</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.content.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>content</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.db.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>db</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.debug.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>debug</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.format.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>format</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.headers.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>headers</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.language.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>language</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.lazy-req.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>lazy-req</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.populators.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>populators</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.remote-ip.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>remote-ip</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.roles.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>roles</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.middleware.session.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>session</span></div></a></li><li class="depth-4"><a href="amelinium.http.middleware.validators.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>validators</span></div></a></li><li class="depth-3 branch"><a href="amelinium.http.router.html"><div class="inner"><span class="tree" style="top: -424px;"><span class="top" style="height: 433px;"></span><span class="bottom"></span></span><span>router</span></div></a></li><li class="depth-3"><a href="amelinium.http.server.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>server</span></div></a></li><li class="depth-4 branch"><a href="amelinium.http.server.jetty.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>jetty</span></div></a></li><li class="depth-4"><a href="amelinium.http.server.undertow.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>undertow</span></div></a></li><li class="depth-2"><a href="amelinium.i18n.html"><div class="inner"><span class="tree" style="top: -672px;"><span class="top" style="height: 681px;"></span><span class="bottom"></span></span><span>i18n</span></div></a></li><li class="depth-3"><a href="amelinium.i18n.pluralizers.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>pluralizers</span></div></a></li><li class="depth-2 branch"><a href="amelinium.identity.html"><div class="inner"><span class="tree" style="top: -52px;"><span class="top" style="height: 61px;"></span><span class="bottom"></span></span><span>identity</span></div></a></li><li class="depth-2 branch"><a href="amelinium.locale.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>locale</span></div></a></li><li class="depth-2 branch"><a href="amelinium.logging.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>logging</span></div></a></li><li class="depth-2"><div class="no-link"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>model</span></div></div></li><li class="depth-3 branch"><a href="amelinium.model.confirmation.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>confirmation</span></div></a></li><li class="depth-3"><a href="amelinium.model.user.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>user</span></div></a></li><li class="depth-2"><div class="no-link"><div class="inner"><span class="tree" style="top: -83px;"><span class="top" style="height: 92px;"></span><span class="bottom"></span></span><span>proto</span></div></div></li><li class="depth-3 branch"><a href="amelinium.proto.auth.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>auth</span></div></a></li><li class="depth-3 branch"><a href="amelinium.proto.errors.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>errors</span></div></a></li><li class="depth-3 branch"><a href="amelinium.proto.identity.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>identity</span></div></a></li><li class="depth-3 branch"><a href="amelinium.proto.session.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>session</span></div></a></li><li class="depth-3"><a href="amelinium.proto.twilio.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>twilio</span></div></a></li><li class="depth-2 branch"><a href="amelinium.schemas.html"><div class="inner"><span class="tree" style="top: -176px;"><span class="top" style="height: 185px;"></span><span class="bottom"></span></span><span>schemas</span></div></a></li><li class="depth-2"><div class="no-link"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>server</span></div></div></li><li class="depth-3"><a href="amelinium.server.ssl.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>ssl</span></div></a></li><li class="depth-2 branch"><a href="amelinium.system.html"><div class="inner"><span class="tree" style="top: -52px;"><span class="top" style="height: 61px;"></span><span class="bottom"></span></span><span>system</span></div></a></li><li class="depth-2"><div class="no-link"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>types</span></div></div></li><li class="depth-3 branch"><a href="amelinium.types.auth.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>auth</span></div></a></li><li class="depth-3 branch"><a href="amelinium.types.db.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>db</span></div></a></li><li class="depth-3 branch"><a href="amelinium.types.errors.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>errors</span></div></a></li><li class="depth-3 branch"><a href="amelinium.types.identity.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>identity</span></div></a></li><li class="depth-3 branch"><a href="amelinium.types.session.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>session</span></div></a></li><li class="depth-3"><a href="amelinium.types.twilio.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>twilio</span></div></a></li><li class="depth-2 branch"><a href="amelinium.utils.html"><div class="inner"><span class="tree" style="top: -207px;"><span class="top" style="height: 216px;"></span><span class="bottom"></span></span><span>utils</span></div></a></li><li class="depth-2"><a href="amelinium.web.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>web</span></div></a></li><li class="depth-3"><a href="amelinium.web.controller.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>controller</span></div></a></li><li class="depth-4 branch"><a href="amelinium.web.controller.admin.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>admin</span></div></a></li><li class="depth-4"><a href="amelinium.web.controller.user.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>user</span></div></a></li><li class="depth-3 branch"><a href="amelinium.web.js.html"><div class="inner"><span class="tree" style="top: -83px;"><span class="top" style="height: 92px;"></span><span class="bottom"></span></span><span>js</span></div></a></li><li class="depth-3 branch"><a href="amelinium.web.taggers.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>taggers</span></div></a></li><li class="depth-3"><a href="amelinium.web.url.html"><div class="inner"><span class="tree"><span class="top"></span><span class="bottom"></span></span><span>url</span></div></a></li></ul></div><div class="document" id="content"><div class="doc"><div class="markdown"><h1><a href="#introduction" id="introduction"></a>Introduction</h1>
<p><strong>Opinionated Clojure Web Engine.</strong></p>
<p>Welcome to Amelinium, yet another set of libraries and helper functions to serve a dynamic web content. It is quite opinionated since its primary purpose is to be the web and API engine for a bunch of projects run by <em>random:seed</em>, the author and associates.</p>
<h2><a href="#important-features" id="important-features"></a>Important features</h2>
<ul>
<li>
<p><strong><a href="https://randomseed.io/software/amelinium/20_hypermedia">JSP model-2, Hypermedia-driven</a> architecture</strong> with <strong>models</strong>, <strong>controllers</strong>, <strong>views</strong> and <strong>layouts</strong>.</p>
</li>
<li>
<p>Configurable with <strong><a href="https://github.com/randomseed-io/amelinium/tree/main/resources/config/amelinium">EDN files</a></strong> loaded from specified directories.</p>
</li>
<li>
<p><a href="https://randomseed.io/software/amelinium/amelinium.app">System management</a> functions, including <strong>inspection</strong> of current state, <strong>starting/stopping</strong> and <strong>suspending</strong>.</p>
</li>
<li>
<p>Database <strong><a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L29">connection pooling</a></strong> and <strong><a href="https://github.com/randomseed-io/amelinium/tree/main/resources/migrations/amelinium">migrations</a></strong>.</p>
</li>
<li>
<p><strong>Abstract caches</strong> around certain <a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L49">database operations</a> (including incremental updates of collections) and time consuming functions, with adjustable TTL and queue size parameters.</p>
</li>
<li>
<p><strong>Internationalization</strong> (i18n) based on <a href="https://github.com/randomseed-io/amelinium/blob/main/resources/translations/amelinium/en.edn">maps</a> with or without missing key messages and <strong><a href="https://github.com/randomseed-io/amelinium/blob/main/src/amelinium/i18n/pluralizers.clj">pluralization rules</a></strong> for translations of countable items.</p>
</li>
<li>
<p><strong><a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L82">Session handling</a></strong> with configurable accessors, secure tokens and prolongation support.</p>
</li>
<li>
<p><strong>Cookie-less</strong> session handling.</p>
</li>
<li>
<p><a href="https://htmx.org/">HTMX</a>-driven <strong>Hypermedia as the Engine of Application State</strong> (<a href="https://htmx.org/essays/hypermedia-apis-vs-data-apis/">HATEOAS</a>) support.</p>
</li>
<li>
<p><strong><a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L96">Role-based access control</a></strong> middleware with optional context detection.</p>
</li>
<li>
<p><strong><a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L78">HTTP headers manipulation</a></strong> middleware.</p>
</li>
<li>
<p><strong><a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L143">Language detection</a></strong> middleware with configurable <a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L124">detection chains</a>.</p>
</li>
<li>
<p><strong>Generic <a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L210">populating functions</a></strong> to enrich request map with dynamic data.</p>
</li>
<li>
<p><strong><a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L72">Remote IP</a></strong> middleware with proxy detection and proxy IP <a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config-ip.edn">whitelisting</a>.</p>
</li>
<li>
<p><strong>Lazy maps</strong> to pass request data between middleware handlers and contextual data to template rendering functions.</p>
</li>
<li>
<p><strong>URI</strong> builders for <strong><a href="https://randomseed.io/software/amelinium/amelinium.common#var-localized-page">localized paths</a></strong> with automatic detection and/or injection of language parameter (route-name based or path based).</p>
</li>
<li>
<p><strong>URI</strong> builders for other <strong><a href="https://randomseed.io/software/amelinium/amelinium.common#var-parameterized-page">parameterized paths</a></strong>.</p>
</li>
<li>
<p><strong>Parameter <a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L153">coercion</a></strong> and <strong>error handling</strong> for both <a href="https://randomseed.io/software/amelinium/amelinium.api.controller#var-handle-coercion-error">API</a> and <a href="https://randomseed.io/software/amelinium/amelinium.web.controller#var-handle-coercion-error">web</a> channels.</p>
</li>
<li>
<p>Built-in coercion and validation schemas for common data types, including <strong>e-mail</strong> addresses, <strong>phone numbers</strong>, <strong>IP addresses</strong> (IPv4, IPv6, mapped IPv4 and mixed), <strong>passwords</strong>, natural person <strong>names</strong>, <strong>instant</strong> values, <strong>duration</strong>, <strong>session IDs</strong> and <strong>MD5 strings</strong>.</p>
</li>
<li>
<p>Built-in dynamic schemas for configurable data sets, including <strong>supported languages</strong> and <strong>account types</strong>.</p>
</li>
<li>
<p><strong>Twilio API</strong> client (for SMS and e-mail messaging, including internationalized templates).</p>
</li>
<li>
<p><strong>API response building</strong> macros and functions (including standardized translatable statuses, sub-statuses, language parameters and errors).</p>
</li>
<li>
<p><strong>Web rendering</strong> handlers and predefined template tags for translations, language-parameterized path generation, session links (for cookie-less sessions), form building and session data access.</p>
</li>
<li>
<p>API and web <strong>response handlers</strong> (controlled by <code>:response/status</code> or <code>:response/fn</code>, <code>:response/headers</code> and <code>:response/body</code> request map entries).</p>
</li>
<li>
<p><strong>Flexible authentication engine</strong> with pluggable <a href="https://randomseed.io/software/amelinium/amelinium.auth.algo.scrypt">encryption modules</a> and ability to build parameterized <a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L341">password encryption chains</a> stored in a database (and re-used where possible).</p>
</li>
<li>
<p><strong>Authentication functions</strong> for API and web, ready to be used in controllers.</p>
</li>
<li>
<p><strong>Identity management functions</strong> for e-mails and phone numbers (with confirmation via a link or code).</p>
</li>
<li>
<p><strong>Buffered events logging</strong> to a database with async channels.</p>
</li>
<li>
<p><strong>Swagger</strong> support for API and web.</p>
</li>
</ul>
<h2><a href="#simplified-request-processing-workflow" id="simplified-request-processing-workflow"></a>Simplified request processing workflow</h2>
<p>Below is the simplified request processing workflow for a web channel. It may shed some light on the overall architecture, especially when someone is new to Clojure way of handling HTTP with Ring abstraction.</p>
<p><img src="doc/img/amelinium-workflow-web.svg" alt="Web request processing in Amelinium" /></p>
<p>The HTTP server handles incoming connections with TCP sockets and negotiates connection parameters. When the bi-directional stream is ready the control is passed to a specified <a href="https://github.com/randomseed-io/amelinium/blob/main/src/amelinium/http/server/undertow.clj#L142">handler function</a> in Clojure, which in this case is <a href="https://github.com/randomseed-io/amelinium/blob/main/src/amelinium/http/handler.clj#L39">Reitit’s Ring handler</a> <a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L273">configured</a> to use Reitit <a href="https://github.com/randomseed-io/amelinium/blob/main/src/amelinium/http/router.clj#L28">HTTP router</a>.</p>
<p>As we can imagine, HTTP router is responsible for matching resource paths from URL strings requested by clients and associating them with configured <a href="">controllers</a> and <a href="https://github.com/randomseed-io/amelinium/blob/main/resources/config/amelinium/config.edn#L236">middleware chains</a>.</p>
<p>Middleware chains are sequences of functions composed into a single execution chain to process request and/or response data. Controllers in Amelinium are handlers responsible for generating content after performing business-logic operations which may involve interacting with a database (via models) or connecting to other services.</p>
<p>Initial context created by the Ring handler is a map populated with basic information, like parameters received from a client, remote IP address, requested path and so on. This <strong>request map</strong> is then passed as an argument to the first function of a middleware chain, and finally to a controller assigned in configuration to a specific HTTP path (route).</p>
<p>Each middleware wrapper and controller can alter the request map. The last middleware (called <em>web renderer</em> on the diagram) is responsible for generating a <strong>response map</strong> on a basis of data found under some special keys in the request map. The entries identified by these keys should be set by the invoked controller (or, in rare cases by some middleware wrapper which may short-circuit the call chain and generate response).</p>
<p>Note that in other middleware setups you will most likely find the route handlers being functions which used to process a request map and return a response map. The difference here (in Amelinium) is that controllers are suppose to add some keys (like <code>:response/status</code> and <code>:response/body</code>) to the received map and the response generation is handled later by the generic renderers (separate for web and API channels). Of course, if any controller will generate a response map, it will be honored and the rendering layer will be skipped. Same with any middleware request wrapper.</p>
<p>When response map is returned it starts to “travel” through all middleware functions in chain to be processed before returning to a Ring handler. The difference between middleware function which transforms a request map from the middleware function which transforms a response map is only in the place where the processing is done. Request-related wrappers will operate on a request argument <strong>before</strong> other middleware wrappers are to be called, and response-related wrappers will operate on a result of calling all other wrappers. This is possible because Ring architecture is based on higher-order functions wrapping the actual logic.</p>
<p>Below is an example of a middleware wrapper which alters a request map by adding <code>:REQ</code> key to it, and does the same thing with a response map by adding a <code>:RESP</code> key to it. Note the <code>(handler request)</code> call which will call all other wrappers in a chain.</p>
<pre><code class="language-clojure">(fn [handler]
(fn [request]
(let [request (assoc request :REQ true) ;; request processing
response (handler request) ;; calling other handlers
response (assoc response :RESP true)] ;; response processing
response)))
</code></pre>
<p>Some middleware wrappers will never care about doing something with the response, therefore being much simpler:</p>
<pre><code class="language-clojure">(fn [handler]
(fn [request]
(handler (assoc request :REQ true))))
</code></pre>
<p>Controllers and middleware wrappers sometimes need access to data other than initially set by the web server. They may want to use external or internal data sources (like database connections or caches) or some additional configuration structures (like translations, data transformation schemas, and so on). One way to make additional objects available to request handling functions is to create a middleware which will then encapsulate some data using a lexical closure and inject it into a request map (by associating with some key). Then, response generating handler can extract value under that key of a request map and continue processing.</p>
<p>The downside of the above approach is quite frequent, unconditional calling of such wrapper (for each processed request) which is not perfect when the injected data were going to be used on rare occasions. In such cases the Reitit router comes very handy since it allows to generate different middleware wrappers or the whole middleware chains for different paths/routes (or even route configurations).</p>
<p>The above is implemented with another layer of abstraction which requires compilation phase when routes are loaded and analyzed. In this process a middleware wrapper may use function closure to keep the configuration associated with the <code>:data</code> key of a particular route. It may even be skipped for certain routes.</p>
<p>Let’s modify our previous example to show how it can be done:</p>
<pre><code class="language-clojure">{:name :turbo-wrapper
:compile (fn [route-data _]
(if-some [rt? (:run-turbo? route-data)] ;; will only exist
(fn [handler] ;; if :run-turbo? was set
(fn [request]
(let [request (assoc request :REQ rt?) ;; request processing
response (handler request) ;; calling other handlers
response (assoc response :RESP rt?)] ;; response processing
response)))))}
</code></pre>
<p>In this example the middleware will be installed only for routes having <code>:run-turbo?</code> set to a truthy value in their route data maps (under <code>:data</code> key). Moreover, a value associated to the <code>:run-turbo?</code> key will be used to do something with a request and with a response.</p>
<h2><a href="#tech-stack" id="tech-stack"></a>Tech stack</h2>
<p>Amelinium is based on the following Clojure and Java libraries:</p>
<ul>
<li>
<p>Application management:</p>
<ul>
<li><a href="https://github.com/weavejester/integrant">Integrant</a> for building and managing systems</li>
<li><a href="https://github.com/metosin/maailma">Maailma</a> for reading EDN configuration files</li>
</ul>
</li>
<li>
<p>HTTP:</p>
<ul>
<li><a href="https://github.com/ring-clojure/ring">Ring</a> for HTTP server abstraction</li>
<li><a href="https://github.com/metosin/reitit/">Reitit</a> for HTTP routing</li>
<li><a href="https://github.com/undertow-io/undertow">Undertow</a> and <a href="https://github.com/eclipse/jetty.project">Jetty</a> for serving HTTP traffic</li>
<li><a href="https://github.com/gnarroway/hato">hato</a> for creating HTTP clients</li>
<li><a href="https://github.com/metosin/muuntaja">Muuntaja</a> for HTTP format negotiation, encoding and decoding</li>
</ul>
</li>
<li>
<p>Logging:</p>
<ul>
<li><a href="https://github.com/qos-ch/logback">Logback</a> for logging abstraction</li>
<li><a href="https://github.com/cambium-clojure">Cambium</a> for logging macros and JSON logging</li>
<li><a href="https://github.com/pyr/unilog">Unilog</a> for logging management</li>
<li><a href="https://github.com/clojure/core.async">core.async</a> for buffered operation logging</li>
<li><a href="https://github.com/randomseed-io/utils">random:utils</a> for logging macros, predefined encoders and appenders</li>
</ul>
</li>
<li>
<p>I18n and l10n:</p>
<ul>
<li><a href="https://github.com/tonsky/tongue">Tongue</a> for translations</li>
<li><a href="https://github.com/trptr/java-wrapper">java-wrapper</a> for locale support</li>
</ul>
</li>
<li>
<p>Frontend JavaScript libraries:</p>
<ul>
<li>
<p><a href="https:/htmx.org/">HTMX</a> for access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML</p>
</li>
<li>
<p><a href="https://hyperscript.org/">_hyperscript</a> for enhancing HTML with concise DOM, event and async features</p>
</li>
</ul>
</li>
<li>
<p>Templates:</p>
<ul>
<li><a href="https://github.com/yogthos/Selmer">Selmer</a> for rendering HTML views and layouts</li>
</ul>
</li>
<li>
<p>Parameters coercion and validation:</p>
<ul>
<li><a href="https://github.com/metosin/malli">Malli</a> for data-driven schemas support</li>
<li><a href="https://github.com/apache/commons-validator">Apache Commons Validator</a> for e-mail and domain name parameters</li>
<li><a href="https://github.com/randomseed-io/bankster">Bankster</a> for monetary unit parameters</li>
<li><a href="https://github.com/randomseed-io/phone-number">phone-number</a> for phone number parameters</li>
</ul>
</li>
<li>
<p>Encryption:</p>
<ul>
<li><a href="https://github.com/funcool/buddy-core">buddy-core</a> for cryptographic operations</li>
<li><a href="https://github.com/wg/crypto">LambdaWorks Crypto</a> for high-performance key derivation functions</li>
<li><a href="https://github.com/weavejester/crypto-equality">crypto-equality</a> for secure comparisons</li>
</ul>
</li>
<li>
<p>Data manipulation and data formats:</p>
<ul>
<li><a href="https://github.com/raxod502/lazy-map">lazy-map</a> for lazy maps</li>
<li><a href="https://github.com/weavejester/meta-merge">Meta-Merge</a> for merging nested structures</li>
<li><a href="https://github.com/metosin/jsonista">jsonista</a> for JSON handling</li>
<li><a href="https://github.com/dakrone/cheshire">Cheshire</a> for JSON handling</li>
<li><a href="https://github.com/clojurewerkz/balagan">Balagan</a> for nested structures matching</li>
<li><a href="https://github.com/clj-commons/camel-snake-kebab">camel-snake-kebab</a> for word case conversions</li>
<li><a href="https://github.com/danlentz/clj-uuid">clj-uuid</a> for RFC4122 unique identifiers</li>
<li><a href="https://github.com/randomseed-io/smangler">smangler</a> for string truncation</li>
<li><a href="https://github.com/randomseed-io/bankster">Bankster</a> for money and currency handling</li>
<li><a href="https://github.com/randomseed-io/phone-number">phone-number</a> for phone numbers handling</li>
<li><a href="https://github.com/randomseed-io/utils">random:utils</a> for fast operations on maps and vectors</li>
</ul>
</li>
<li>
<p>Databases:</p>
<ul>
<li><a href="https://github.com/seancorfield/next-jdbc">next.jdbc</a> for accessing SQL databases</li>
<li><a href="https://github.com/brettwooldridge/HikariCP">HikariCP</a> for database connection pooling</li>
<li><a href="https://github.com/weavejester/ragtime">Ragtime</a> for database migrations</li>
<li><a href="https://github.com/ptaoussanis/nippy">Nippy</a> for fast serialization</li>
<li><a href="https://github.com/clojure/core.cache">core.cache</a> and <a href="https://github.com/clojure/core.memoize">core.memoize</a> for function memoization</li>
<li><a href="https://github.com/randomseed-io/utils">random:utils</a> for property caching</li>
</ul>
</li>
<li>
<p>Time:</p>
<ul>
<li><a href="https://github.com/juxt/tick">Tick</a> for dealing with time</li>
</ul>
</li>
</ul>
<h2><a href="#installation" id="installation"></a>Installation</h2>
<p><a href="https://clojars.org/io.randomseed/amelinium"><img src="https://img.shields.io/clojars/v/io.randomseed/amelinium.svg" alt="Amelinium on Clojars" /></a> <a href="https://cljdoc.org/d/io.randomseed/amelinium/CURRENT"><img src="https://cljdoc.org/badge/io.randomseed/amelinium" alt="Amelinium on cljdoc" /></a> <a href="https://circleci.com/gh/randomseed-io/amelinium"><img src="https://circleci.com/gh/randomseed-io/amelinium.svg?style=svg" alt="CircleCI" /></a></p>
<p>To use Amelinium in your project, add the following to dependencies section of <code>project.clj</code> or <code>build.boot</code>:</p>
<pre><code class="language-clojure">[io.randomseed/amelinium "1.0.1"]
</code></pre>
<p>For <code>deps.edn</code> add the following as an element of a map under <code>:deps</code> or <code>:extra-deps</code> key:</p>
<pre><code class="language-clojure">io.randomseed/amelinium {:mvn/version "1.0.1"}
</code></pre>
<p>Additionally, if you want to utilize specs and generators provided by the Amelinium you can use (in your development profile):</p>
<pre><code class="language-clojure">org.clojure/spec.alpha {:mvn/version "0.2.194"}
org.clojure/test.check {:mvn/version "1.1.0"}
</code></pre>
<p>You can also download JAR from <a href="https://clojars.org/io.randomseed/amelinium">Clojars</a>.</p>
<h2><a href="#sneak-peeks" id="sneak-peeks"></a>Sneak peeks</h2>
<p>TBW</p>
<p>And more…</p>
<h2><a href="#documentation" id="documentation"></a>Documentation</h2>
<p>Full documentation including usage examples is available at:</p>
<ul>
<li><a href="https://randomseed.io/software/amelinium/">https://randomseed.io/software/amelinium/</a></li>
</ul>
<h2><a href="#license" id="license"></a>License</h2>
<p>Copyright © 2022-2023 Paweł Wilk</p>
<p>May contain works from earlier free software projects, copyright © 2019-2022 Paweł Wilk</p>
<p>Amelinium is copyrighted software owned by Paweł Wilk (<a href="mailto:[email protected]">[email protected]</a>). You may redistribute and/or modify this software as long as you comply with the terms of the <a href="https://github.com/randomseed-io/amelinium/blob/master/LICENSE">GNU Lesser General Public License</a> (version 3).</p>
<p>Amelinium is delivered with the HTMX and _hyperscript JavaScript libraries licensed under <a href="https://github.com/randomseed-io/amelinium/blob/master/HTMX-LICENSE">BSD-2-Clause license</a>.</p>
<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>
<p>THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>
<h2><a href="#development" id="development"></a>Development</h2>
<h3><a href="#building-docs" id="building-docs"></a>Building docs</h3>
<pre><code class="language-bash">make docs
</code></pre>
<h3><a href="#building-jar" id="building-jar"></a>Building JAR</h3>
<pre><code class="language-bash">make jar
</code></pre>
<p>### Rebuilding POM</p>
<pre><code class="language-bash">make pom
</code></pre>
<h3><a href="#signing-pom" id="signing-pom"></a>Signing POM</h3>
<pre><code class="language-bash">make sig
</code></pre>
<h3><a href="#deploying-to-clojars" id="deploying-to-clojars"></a>Deploying to Clojars</h3>
<pre><code class="language-bash">make deploy
</code></pre>
<h3><a href="#interactive-development" id="interactive-development"></a>Interactive development</h3>
<pre><code class="language-bash">bin/repl
</code></pre>
<p>Starts REPL and nREPL server (port number is stored in <code>.nrepl-port</code>).</p>
</div></div></div></body></html>