A "from first principles" variant of "usermanager-example", the tutorial Clojure web application by Sean Corfield.
OTHER License
This app is a stripped-down variant of Sean Corfield's usermanager-example project (synced as of commit SHA 2a9cf63).
Sean's original "User Manager" example project and its variants (including this one), aim to demystify "How to construct a Clojure web application using only libraries?".
"Composition over inheritance", "Libraries over frameworks", and "Data orientation" feature prominently in the Clojure world's canonical mental model of programming, including programming the web. Absent some of these key intuitions, even experienced developers who are new to Clojure tend to struggle to build apps using libraries. The "User Manager" collective of demos aim to address this specific challenge. They do not aspire to teach web development best practices. To that end, all "User Manager" examples are small, simplified, "Web 1.0" style apps.
This project expands upon the "from first principles" primer I wrote down in "Clojuring the web application stack: Meditation One". I chose to use Sean's original demo app as a specification because:
If nothing else, it exists to scratch one's own itch... I like barebones explanations and love going down "wait, but why?" rabbit holes and tangents.
You see, all the other "User Manager" demos, though simple, are built with libraries used by Clojure professionals in real-world production web apps. So, if libraries are a precursor to one's (custom) web stack (or framework)... what would be the precursor to libraries? No libraries :)
Keep handy the original User Manager", and my blog post, for side-by-side reference as you work through this codebase.
If anything is unclear, or in error, please feel free to open an issue (but please don't change the structure of the code).
I have crafted it to help the reader observe the piece-by-piece "making-of" of the app. Consult the README as of the commit in question for usage instructions relevant to that point in development.
I think we learn better by progressively going from approximate coarse-grained mental models to high-fidelity ones. As such, some deliberate simplifications may annoy web professionals (like using GET to delete). One trusts learners to pick up the "right" ways to do things from the sum total of their studies, experiments, colleagues, and mentors.
When learning, it's sometimes good to be RESTless ;)
What to expect, if you work forward from the very first commit:
curl
requests to the app.
curl localhost:3000
curl -XPOST localhost:3000/some/path?q=somethingsomething
curl -XDELETE localhost:3000/foo/bar/baz
usermanager.main
namespace should always have some current-as-of-the-commit way to start/stop the server process (and/or current state of the app).usermanager.*
namespace, that's a clue.usermanager.main
should ease restoring your server, after restarting the REPL.dev/
directory (you may have to manually create it).department
and addressbook
).dev
database...
Like I mentioned, I've subtracted as many libraries as I could, without compromising fidelity to the original project's design. The exception is, any form of host interop between our web app and the outside world. I've assumed pre-existing solutions (libraries) for those needs (I have to draw a boundary somewhere!). Also some creature comfort utilities that aren't central to the theme of this first-principles explanation.
Hark back to the blog post to get a better picture of where and why I've drawn this boundary.
Specifically, I've used:
To stay true to Sean's specification for usermanager's API, domain model, and core "business logic", I have straight-copied parts of his usermanager-example source:
To reinforce the idea of composing moving parts using plain Clojure data, I have crafted my code to use the design choices made by Sean (e.g. injecting the name of the view in request context, for later use by HTML rendering logic). Likewise, to stay true to the Ring specification, all self-written Ring utilities and middleware follow the Ring spec. Replacing them with Ring-provided originals should be straightforward.
If you choose to write your own variant, I suggest following suit.
It should be obvious by now, but I will state it anyway... The stripped down app is not at all robust to real-world demands because it omits nearly all the libraries used by the other "User Manager" examples, that do all the "production grade" heavy lifting for us. Even those apps will need some work done on them (design reviews, integration testing, security checks etc.), if they are to qualify for real-world deployment.
Same as Sean's usermanager-example project.
deps.edn
file.tools.build
library to use commands from the build.clj
file. It is included via the :build
alias of the deps.edn
file. Clojure-cli-using projects use such a build.clj
file by convention, to provide standard and custom project build functionality. Project skeleton setup tools typically auto-generate this file. I've copied it over from Sean's project.Clone the repo, cd
into it, then follow any of the methods below to try out the app and/or deploy it. Note that the resulting app is NOT fit for production deployment. Feel free to deploy it, of course, but expose it to the Public Internet only on a throwaway server instance.
Run the tests this way, from the root of the project.
clojure -T:build test
This uses the :build
alias to load the build.clj
file, based on tools.build
, and run the test
task.
Hopefully the tests pass! You should see something like this:
Running tests in #{"test"}
[ Many lines of test runner log messages. ]
Ran 11 tests containing 37 assertions.
0 failures, 0 errors.
Note about the log messages:
You may run the app at any point in the commit history of this project. However, the functionality available will only match whatever is built up unto that commit.
Start the app and point your browser to http://localhost:3000.
clojure -M -m usermanager.main
If that port is in use, start it on a different port. For example, port 3100:
clojure -M -m usermanager.main 3100
Start REPL
clj -M:dev:test
Once REPL starts, start the server on the default port (port 3000):
user=> (require 'usermanager.main) ; load the code
user=> (in-ns 'usermanager.main) ; move to the namespace
usermanager.main=> (-main) ; or some other port (-main 8080)
Point your browser to the appropriate URL http://localhost:PORTNUMBER.
Use the dev
and test
profiles when you run the REPL, whether standalone, or via your favourite editor.
Then, eval/apply away!
For server deployment, you typically want to build an "uberjar" -- a .jar
file that contains Clojure itself and all of the code from your application and its dependencies, so that you can run it with the java -jar
command. (But like I stated earlier, this project is not production software. So deploy it only to throwaway server environments.)
The build.clj
file -- mentioned above -- contains a ci
task that:
target
folder.jar
fileclojure -T:build ci
That should produce the same output as test
above, followed by something like:
Copying source...
Compiling usermanager.main...
Building JAR...
The target
folder will be created if it doesn't exist and it will include a classes
folder containing all of the compiled Clojure source code from the usermanager
application and all of its dependencies including Clojure itself:
ls target/classes/
hiccup hiccup2 public ring usermanager
It will also include the standalone .jar
file which you can run like this:
java -jar target/usermanager/example-standalone.jar
This should behave the same as the Run the Application example above.
This JAR file can be deployed to any server that have Java installed and run with no other external dependencies or files.
I might demo how to replace each hand-rolled piece using production Clojure libraries.
But maybe you can do it in your own words, as self-assigned homework! :)
Compare and contrast with those other usermanager-example projects, for clues.
May the Source be with You!
Copyright (c) 2015-2024 Sean Corfield. Copyright (c) 2024 Aditya Athalye.
Distributed under the Apache Source License 2.0.