To run the example app, use the following command:
$ cd applications/example
$ clj -J-Dclojure.tools.logging.factory=clojure.tools.logging.impl/slf4j-factory -M -m example.app -c resources/config.ednThis will start the example app as defined by the Integrant system map in example.app.system namespace
with the configuration from resources/config.edn (thanks to functionality provided by
the module setup/CLI).
The sample app will log to a file app.log in a sibling directory of example named example.logs.
$ cat ../example.logs/app.log
{"ns":"example.app.system","file":"example/app/system.clj","msg":"Hello","hostname":"<hostname>","level":"error","line":7,"thread":"main","timestamp":"2025-03-10T15:49:27Z","to":"World"}
{"msg":"Shutting down system...","timestamp":"2025-03-10T15:49:27Z","level":"warn","thread":"Thread-1","file":"example/app.clj","line":32,"ns":"example.app","hostname":"<hostname>"}
{"msg":"System shut down.","timestamp":"2025-03-10T15:49:27Z","level":"warn","thread":"Thread-1","file":"example/app.clj","line":34,"ns":"example.app","hostname":"<hostname>"}
{"msg":"Shutting down clojure agents.","timestamp":"2025-03-10T15:49:27Z","level":"warn","thread":"Thread-1","file":"gorillalabs/bauhaus/setup/shutdown.clj","line":123,"ns":"gorillalabs.bauhaus.setup.shutdown","hostname":"<hostname>"}Logging is initialized using the ../modules/setup/logging module, so you can just use
timbre for logging and configuring the logging, no matter what any of the libraries you require decided to use. The most
common Java logging frameworks are supported via SLF4J.
For clojure.tools.logging support, you need to setup the JVM properly -Dclojure.tools.logging.factory=clojure.tools.logging.impl/slf4j-factory (see deps.edn dev alias :jvm-opts). Above, we were
using the -J option to pass JVM options to the JVM (see Clojure CLI Reference).
Logging is setup in example.app/-main using
(require '[[gorillalabs.bauhaus.setup.logging :as setup.logging]])
(setup.logging/init-logging (setup.logging/log-config "../example.logs/app.log"))As you can see from the logging output, Bauhaus cares for a clean shutdown of the system (using module setup-shutdown), and logs this to the log file.
This is realized in the example.app/-main function using
(require '[gorillalabs.bauhaus.setup.shutdown :as setup.shutdown])
(setup.shutdown/add-hook! :clojure.core/shutdown-agents setup.shutdown/agents)
...
(setup.shutdown/add-hook! :app/shutdown-system shutdown)
(setup.shutdown/hook-order! [:app/shutdown-system :clojure.core/shutdown-agents]) The example app is setup to open a Socket REPL if a repl port is configured (see example.app/-main).
Here, this is rather pointless (as the app will shutdown immediately after starting), but it is a good example to show how to setup a Socket REPL:
(when (config/repl-port config)
(let [socket (clojure.core.server/start-server {:port (config/repl-port config)
:name "socket-repl"
:accept 'clojure.core.server/repl})]
(log/info "Started REPL socket server" {:socket socket})))Fire up a REPL for the example application to follow along the Bauhaus introduction, using
dev alias. Use your favorite toolset for this (mine is Cursive), or just use the CLI:
$ cd applications/example
$ clj -A:devBauhaus projects (as the example project) are Integrant-based. The example project has extensive
dev support (see the dev and dev-resources folders): Dev integrates the
Integrant dev tooling into the user namespace, so from the user namespace you can just run
(go)to startup the application.
This will
- read the (developer) configuration from
dev-resources/config.edn(utilizing the Aero library for configuration) - setup the Integrant system map (merging default configuration and development configuration)
- start the system
You will see the log output greeting with a message like this:
user=> (go)
2025-03-10T14:29:57.285Z <hostname> DEBUG [dev.system:15] - Read dev config
2025-03-10T14:29:57.316Z <hostname> DEBUG [dev.system:15] - Read dev config
2025-03-10T14:29:57.320Z <hostname> INFO [dev.system:12] - Hallo {:an "christianbetz"}
:initiated
To reload the system (see Integrant REPL), just run (reset) in the user namespace. This will reload your develoment configuration.
Now: Feel free to hack away!
A second example uses a simple http service. You can start the application similar to how you started the simple example app:
$ cd applications/example-http-service
$ clj -J-Dclojure.tools.logging.factory=clojure.tools.logging.impl/slf4j-factory -M -m example-http.app -c resources/config.ednThe log file will tell you when the system is started:
$ tail ../example-http-service.logs/app.log
...
{"msg":"System started","timestamp":"2025-03-10T16:28:22Z","level":"info","thread":"main","file":"example_http/app.clj","line":56,"ns":"example-http.app","hostname":"<hostname>"}
You can then test the application using cURL:
$ curl -vv -H "Accept: application/json" -H "accept-charset: iso-8859-1" http://localhost:8080/bar/
* Host localhost:8080 was resolved.
...
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=iso-8859-1
< content-length: 40
< Server: http-kit
{"msg":"Hello World ?","status":"green"}As with the simple example, the Integrant system is set up in the [example-http/system]
(../applications/example-http-service/src/example_http/system.clj) namespace:
It uses the :gorillalabs.bauhaus.http/server component provided by the [http-server](../modules/http-server) module as well as the :gorillalabs.bauhaus.http-server/auth-backend`, also provided by http-server module.
The example app itself defines two components: :gorillalabs.bauhaus.http-example/api and
:gorillalabs.bauhaus.http-example/core, showcasing the separation of a functional core and an http API as a port in a
hexagonal architecture.
The example http app provides routes where the caller needs to authenticate using Entra ID. Bauhaus provides a module to setup Entra ID authentication and acquire a token, see the auth/entra-id/entra-identity module. However, we will not discuss in detail here in the Intro.
We configured a different http port for the dev configuration in the dev-resources/dev-config.edn file, so we can
run the application in the REPL parallel to a running application.
Start a REPL (as described above) and run (go) in the user namespace. This will start the application with the dev configuration:
$ cd applications/example-http-service
$ clj -A:devYou can then test the REPL based application using cURL as described above, but with a different port:
$ curl -vv -H "Accept: application/json" -H "accept-charset: iso-8859-1" http://localhost:8000/bar/
{"msg":"Hello Developer ????","status":"purple"}Notice the different message. That's because the dev system map set up a different functional core in the [example-http.dev.system/dev-system]
(../applications/example-http-service/dev/example_http/dev/system.clj) function.
Feel free to change that message by altering the function returned by the ig/init-key :http-example.dev/core method in the
example-http.dev.core namespace and reload the system by evaluating (reset) in the user namespace.
Again: Feel free to hack away and have fun!