[uberjar]: lifecycle taking fns and values
This commit is contained in:
parent
897f997630
commit
18e4e229ce
14 changed files with 22 additions and 519 deletions
12
README.md
12
README.md
|
|
@ -72,16 +72,16 @@ mount is an alternative to the [component](https://github.com/stuartsierra/compo
|
|||
Creating state is easy:
|
||||
|
||||
```clojure
|
||||
(defstate conn :start (create-conn))
|
||||
(defstate conn :start create-conn)
|
||||
```
|
||||
|
||||
where `(create-conn)` is defined elsewhere, can be right above it.
|
||||
where `create-conn` function is defined elsewhere, can be right above it.
|
||||
|
||||
In case this state needs to be cleaned / destryed between reloads, there is also `:stop`
|
||||
|
||||
```clojure
|
||||
(defstate conn :start (create-conn)
|
||||
:stop (disconnect conn))
|
||||
(defstate conn :start create-conn
|
||||
:stop #(disconnect conn))
|
||||
```
|
||||
|
||||
That is pretty much it. But wait, there is more.. this state is _a top level being_, which means it can be simply
|
||||
|
|
@ -129,7 +129,7 @@ There are of course direct dependecies that `mount` respects:
|
|||
(:require [mount.core :refer [defstate]]))
|
||||
|
||||
(defstate app-config
|
||||
:start (load-config "test/resources/config.edn"))
|
||||
:start #(load-config "test/resources/config.edn"))
|
||||
```
|
||||
|
||||
this `app-config`, being top level, can be used in other namespaces, including the ones that create states:
|
||||
|
|
@ -139,7 +139,7 @@ this `app-config`, being top level, can be used in other namespaces, including t
|
|||
(:require [mount.core :refer [defstate]]
|
||||
[app.config :refer [app-config]]))
|
||||
|
||||
(defstate conn :start (create-connection app-config))
|
||||
(defstate conn :start #(create-connection app-config))
|
||||
```
|
||||
|
||||
[here](https://github.com/tolitius/mount/blob/master/test/app/nyse.clj)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
(ns dev
|
||||
"Tools for interactive development with the REPL. This file should
|
||||
not be included in a production build of the application."
|
||||
;; (:use [cljs.repl :only [repl]]
|
||||
;; [cljs.repl.browser :only [repl-env]])
|
||||
(:require [clojure.java.io :as io]
|
||||
[clojure.java.javadoc :refer [javadoc]]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
|
|
@ -11,7 +9,7 @@
|
|||
[clojure.set :as set]
|
||||
[clojure.string :as str]
|
||||
[clojure.test :as test]
|
||||
;; [clojure.core.async :refer [>!! <!! >! <! go-loop alt! timeout]]
|
||||
[app.utils.logging :refer [with-logging-status]]
|
||||
[clojure.tools.namespace.repl :as tn]
|
||||
[mount.core :as mount]
|
||||
[app.www]
|
||||
|
|
@ -43,3 +41,5 @@
|
|||
[]
|
||||
(stop)
|
||||
(tn/refresh :after 'dev/go))
|
||||
|
||||
(with-logging-status) ;; to demo REPL time logging
|
||||
|
|
|
|||
|
|
@ -1,197 +0,0 @@
|
|||
###### Differences from "Component"
|
||||
|
||||
## Perception
|
||||
|
||||
Solving the "application state" in Clojure, where an application is not a tool or a library,
|
||||
but a product that has lots of state to deal with, is not a trivial task.
|
||||
The [Component](https://github.com/stuartsierra/component) framework is a solution that has been gaining popularity:
|
||||
|
||||
> _[source](http://www.javacodegeeks.com/2015/09/clojure-web-development-state-of-the-art.html):_
|
||||
|
||||
> _I think all agreed that Component is the industry standard for managing lifecycle of Clojure applications. If you are a Java developer you may think of it as a Spring (DI) replacement – you declare dependencies between “components” which are resolved on “system” startup. So you just say “my component needs a repository/database pool” and component library “injects” it for you._
|
||||
|
||||
While this is a common understanding, the Component is far from being Spring, in a good sense:
|
||||
|
||||
* its codebase is fairly small
|
||||
* it aims to solve one thing and one thing only: manage application state via inversion of control
|
||||
|
||||
The not so hidden benefit is REPL time reloadability that it brings to the table with `component/start` and `component/stop`
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
|
||||
|
||||
- [Then why "mount"!?](#then-why-mount)
|
||||
- [So what are the differences?](#so-what-are-the-differences)
|
||||
- [Objects vs. Namespaces](#objects-vs-namespaces)
|
||||
- [Start and Stop Order](#start-and-stop-order)
|
||||
- [Component requires whole app buy in](#component-requires-whole-app-buy-in)
|
||||
- [Refactoring an existing application](#refactoring-an-existing-application)
|
||||
- [Code navigation](#code-navigation)
|
||||
- [Starting and stopping _parts_ of an application](#starting-and-stopping-_parts_-of-an-application)
|
||||
- [Boilerplate code](#boilerplate-code)
|
||||
- [What Component does better](#what-component-does-better)
|
||||
- [Swapping alternate implementations](#swapping-alternate-implementations)
|
||||
- [Uberjar / Packaging](#uberjar--packaging)
|
||||
- [Multiple separate systems](#multiple-separate-systems)
|
||||
- [Visualizing dependency graph](#visualizing-dependency-graph)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Then why "mount"!?
|
||||
|
||||
[mount](https://github.com/tolitius/mount) was created after using Component for several projects.
|
||||
|
||||
While Component is an interesting way to manage state, it has its limitations that prevented us
|
||||
from having the ultimate super power of Clojure: _fun working with it_. Plus several other disadvantages
|
||||
that we wanted to "fix".
|
||||
|
||||
## So what are the differences?
|
||||
|
||||
### Objects vs. Namespaces
|
||||
|
||||
One thing that feels a bit "unClojure" about Component is "Objects". Objects everywhere, and Objects for everything.
|
||||
This is how Component "separates explicit dependencies" and "clears the bounaries".
|
||||
|
||||
This is also how an Object Oriented language does it, which does not leave a lot of room for functions:
|
||||
with Component most of the functions are _methods_ which is an important distinction.
|
||||
|
||||
Mount relies on Clojure namespaces to clear the boundaries. No change from Clojure here: `defstate` in one namespace
|
||||
can be easily `:require`d in another.
|
||||
|
||||
### Start and Stop Order
|
||||
|
||||
Component relies on a cool [dependency](https://github.com/stuartsierra/dependency) library to build
|
||||
a graph of dependencies, and start/stop them via topological sort based on the dependencies in this graph.
|
||||
|
||||
Since Mount relies on Clojure namespaces and `:require`/`:use`, the order of states
|
||||
and their dependencies are revealed by the Clojure Compiler itself. Mount just records that order and replays
|
||||
it back and forth on stop and start.
|
||||
|
||||
### Component requires whole app buy in
|
||||
|
||||
Component really only works if you build your entire app around its model: application is fully based on Components
|
||||
where every Component is an Object.
|
||||
|
||||
Mount does not require you to "buy anything at all", it is free :) Just create a `defstate` whenever/whereever
|
||||
you need it and use it.
|
||||
|
||||
This one was a big deal for all the projects we used Component with, "the whole app buy in" converts an "_open_" application
|
||||
of Namespaces and Functions to a "_closed_" application of Objects and Methods. "open" and "close"
|
||||
here are rather feelings, but it is way easier and more natural to
|
||||
|
||||
* go to a namespace to see this function
|
||||
than to
|
||||
* go to a namespace, go to a component, go to another component that this function maybe using/referenced at via a component key, to get the full view of the function.
|
||||
|
||||
Again this is mostly a personal preference: the code works in both cases.
|
||||
|
||||
### Refactoring an existing application
|
||||
|
||||
Since to get the most benefits of Component the approach is "all or nothing", to rewrite an existing application
|
||||
in Component, depending on the application size, is daunting at best.
|
||||
|
||||
Mount allows adding `defstates` _incrementally_, the same way you would add functions to an application.
|
||||
|
||||
### Code navigation
|
||||
|
||||
Component changes the way the code is structured. Depending on the size of the code base, and how rich the dependency graph is, Component might add a good amount of cognitive load. To a simple navigation from namespace to namespace, from function to function, Components add, well.. "Components" that can't be ignored when [loading the codebase in one's head](http://paulgraham.com/head.html)
|
||||
|
||||
Since Mount relies on Clojure namespaces (`:require`/`:use`), navigation across functions / states is exactly
|
||||
the same with or without Mount: there are no extra mental steps.
|
||||
|
||||
### Starting and stopping _parts_ of an application
|
||||
|
||||
Component can't really start and stop parts of an application within the same "system". Other sub systems can be
|
||||
created from scratch or by dissoc'ing / merging with existing systems, but it is usually not all
|
||||
that flexible in terms of REPL sessions where lots of time is spent.
|
||||
|
||||
Mount _can_ start and stop parts of an application via given states with their namespaces:
|
||||
|
||||
```clojure
|
||||
dev=> (mount/start #'app.config/app-config #'app.nyse/conn)
|
||||
|
||||
11:35:06.753 [nREPL-worker-1] INFO mount - >> starting.. app-config
|
||||
11:35:06.756 [nREPL-worker-1] INFO mount - >> starting.. conn
|
||||
:started
|
||||
dev=>
|
||||
```
|
||||
|
||||
Here is more [documentation](../README.md#start-and-stop-parts-of-application) on how to start/stop parts of an app.
|
||||
|
||||
### Boilerplate code
|
||||
|
||||
Component does not require a whole lot of "extra" code but:
|
||||
|
||||
* a system with dependencies
|
||||
* components as records
|
||||
* with optional constructors
|
||||
* and a Lifecycle/start Lifecycle/stop implementations
|
||||
* destructuring component maps
|
||||
|
||||
Depending on the number of application components the "extra" size may vary.
|
||||
|
||||
Mount is pretty much:
|
||||
|
||||
```clojure
|
||||
(defstate name :start (fn)
|
||||
:stop (fn))
|
||||
```
|
||||
|
||||
no "ceremony".
|
||||
|
||||
## What Component does better
|
||||
|
||||
### Swapping alternate implementations
|
||||
|
||||
This is someting that is very useful for testing and is very easy to do in Component by simply assoc'ing onto a map.
|
||||
|
||||
Mount can do it to: https://github.com/tolitius/mount#swapping-alternate-implementations
|
||||
|
||||
The reason it is in "Component does it better" section is because, while result is the same, merging maps is a bit simpler than:
|
||||
|
||||
```clojure
|
||||
(mount/start-with {#'app.nyse/db #'app.test/test-db
|
||||
#'app.nyse/publisher #'app.test/test-publisher})
|
||||
```
|
||||
|
||||
### Uberjar / Packaging
|
||||
|
||||
Since Component fully controls the `system` where the whole application lives, it is quite simple
|
||||
to start an application from anywhere including a `-main` function of the uberjar.
|
||||
|
||||
In order to start the whole system in development, Mount just needs `(mount/start)` or `(reset)`
|
||||
it's [simple](https://github.com/tolitius/mount#the-importance-of-being-reloadable).
|
||||
|
||||
However there is no "tools.namespaces"/REPL at a "stand alone jar runtime" and in order for Mount to start / stop
|
||||
the app, states need to be `:require`/`:use`d, which is usually done within the same namespace as `-main`.
|
||||
|
||||
Depending on app dependencies, it could only require a few states to be `:require`/`:use`d, others
|
||||
will be brought transitively. Here is an [example](uberjar.md#creating-reloadable-uberjarable-app) of building a wepapp uberjar with Mount.
|
||||
|
||||
On the flip side, Component _system_ usually requires lots of `:require`s as well, since in order to be built, it needs to "see" all the top level states.
|
||||
|
||||
###### _conclusion: it's simple in Mount as well, but requires an additional step._
|
||||
|
||||
### Multiple separate systems
|
||||
|
||||
With Component multiple separate systems can be started _in the same Clojure runtime_ with different settings. Which is very useful for testing.
|
||||
|
||||
Mount keeps states in namespaces, hence the app becomes "[The One](https://en.wikipedia.org/wiki/Neo_(The_Matrix))", and there can't be "multiples The Ones".
|
||||
|
||||
Testing is not alien to Mount and it knows how to do a thing or two:
|
||||
|
||||
* [starting / stopping parts of an application](https://github.com/tolitius/mount/blob/master/doc/differences-from-component.md#starting-and-stopping-parts-of-an-application)
|
||||
* [start an application without certain states](https://github.com/tolitius/mount#start-an-application-without-certain-states)
|
||||
* and [swapping alternate implementations](https://github.com/tolitius/mount#swapping-alternate-implementations)
|
||||
|
||||
But running two apps in the same JVM side by side with "same but different" states, is not something Mount can do at the moment.
|
||||
|
||||
###### _conclusion: needs more thinking._
|
||||
|
||||
### Visualizing dependency graph
|
||||
|
||||
Component keeps an actual graph which can be visualized with great libraries like [loom](https://github.com/aysylu/loom).
|
||||
Having this visualization is really helpful, especially during code discusions between multiple developers.
|
||||
|
||||
Mount does not have this at the moment. It does have all the data to create such a visualization, perhaps even
|
||||
by building a graph out of the data it has just for this purpose.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
|
|
@ -1,3 +0,0 @@
|
|||
# Introduction to statuo
|
||||
|
||||
TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
## Passing Runtime Arguments
|
||||
|
||||
This example lives in the `with-args` branch. If you'd like to follow along:
|
||||
|
||||
```bash
|
||||
$ git checkout with-args
|
||||
Switched to branch 'with-args'
|
||||
```
|
||||
|
||||
## Start with args
|
||||
|
||||
In order to pass runtime arguments, these could be `-this x -that y` params or `-Dparam=` or
|
||||
just a path to an external configuration file, `mount` has a special `start-with-args` function:
|
||||
|
||||
```clojure
|
||||
(defn -main [& args]
|
||||
(mount/start-with-args args))
|
||||
```
|
||||
|
||||
Most of the time it is better to parse args before they "get in", so usually accepting args would look something like:
|
||||
|
||||
```clojure
|
||||
(defn -main [& args]
|
||||
(mount/start-with-args
|
||||
(parse-args args)))
|
||||
```
|
||||
|
||||
where the `parse-args` is an app specific function.
|
||||
|
||||
### Reading arguments
|
||||
|
||||
Once the arguments are passed to the app, they are available via:
|
||||
|
||||
```clojure
|
||||
(mount/args)
|
||||
```
|
||||
|
||||
Which, unless the arguments were parsed or modified in the `-main` function,
|
||||
will return the original `args` that were passed to `-main`.
|
||||
|
||||
### "Reading" example
|
||||
|
||||
Here is an [example app](https://github.com/tolitius/mount/blob/with-args/test/app/app.clj) that takes `-main` arguments
|
||||
and parses them with [tools.cli](https://github.com/clojure/tools.cli):
|
||||
|
||||
```clojure
|
||||
;; "any" regular function to pass arguments
|
||||
(defn parse-args [args]
|
||||
(let [opts [["-d" "--datomic-uri [datomic url]" "Datomic URL"
|
||||
:default "datomic:mem://mount"]
|
||||
["-h" "--help"]]]
|
||||
(-> (parse-opts args opts)
|
||||
:options)))
|
||||
|
||||
;; example of an app entry point with arguments
|
||||
(defn -main [& args]
|
||||
(mount/start-with-args
|
||||
(parse-args args)))
|
||||
```
|
||||
|
||||
For the example sake the app reads arguments in two places:
|
||||
|
||||
* [inside](https://github.com/tolitius/mount/blob/with-args/test/app/nyse.clj#L17) a `defstate`
|
||||
|
||||
```clojure
|
||||
(defstate conn :start (new-connection (mount/args))
|
||||
:stop (disconnect (mount/args) conn))
|
||||
```
|
||||
|
||||
* and from "any" [other place](https://github.com/tolitius/mount/blob/with-args/test/app/config.clj#L8) within a function:
|
||||
|
||||
```clojure
|
||||
(defn load-config [path]
|
||||
;; ...
|
||||
(if (:help (mount/args))
|
||||
(info "\n\nthis is a sample mount app to demo how to pass and read runtime arguments\n"))
|
||||
;; ...)
|
||||
```
|
||||
|
||||
### "Uber" example
|
||||
|
||||
In order to demo all of the above, we'll build an uberjar:
|
||||
|
||||
```bash
|
||||
$ lein do clean, uberjar
|
||||
...
|
||||
Created .. mount/target/mount-0.2.0-SNAPSHOT-standalone.jar
|
||||
```
|
||||
|
||||
Since we have a default for a Datomic URI, it'll work with no arguments:
|
||||
|
||||
```bash
|
||||
$ java -jar target/mount-0.2.0-SNAPSHOT-standalone.jar
|
||||
|
||||
22:12:03.290 [main] INFO mount - >> starting.. app-config
|
||||
22:12:03.293 [main] INFO mount - >> starting.. conn
|
||||
22:12:03.293 [main] INFO app.nyse - creating a connection to datomic: datomic:mem://mount
|
||||
22:12:03.444 [main] INFO mount - >> starting.. nrepl
|
||||
```
|
||||
|
||||
Now let's ask it to help us:
|
||||
|
||||
```bash
|
||||
$ java -jar target/mount-0.2.0-SNAPSHOT-standalone.jar --help
|
||||
|
||||
22:13:48.798 [main] INFO mount - >> starting.. app-config
|
||||
22:13:48.799 [main] INFO app.config -
|
||||
|
||||
this is a sample mount app to demo how to pass and read runtime arguments
|
||||
|
||||
22:13:48.801 [main] INFO mount - >> starting.. conn
|
||||
22:13:48.801 [main] INFO app.nyse - creating a connection to datomic: datomic:mem://mount
|
||||
22:13:48.946 [main] INFO mount - >> starting.. nrepl
|
||||
```
|
||||
|
||||
And finally let's connect to the Single Malt Database. It's Friday..
|
||||
|
||||
```bash
|
||||
$ java -jar target/mount-0.2.0-SNAPSHOT-standalone.jar -d datomic:mem://single-malt-database
|
||||
|
||||
22:16:10.733 [main] INFO mount - >> starting.. app-config
|
||||
22:16:10.737 [main] INFO mount - >> starting.. conn
|
||||
22:16:10.737 [main] INFO app.nyse - creating a connection to datomic: datomic:mem://single-malt-database
|
||||
22:16:10.885 [main] INFO mount - >> starting.. nrepl
|
||||
```
|
||||
|
||||
### Other usecases
|
||||
|
||||
Depending the requirements, these runtime arguments could take different shapes of forms. You would have a full control
|
||||
over what is passed to the app, the same way you have it without mount through `-main [& args]`.
|
||||
163
doc/uberjar.md
163
doc/uberjar.md
|
|
@ -1,163 +0,0 @@
|
|||
## Creating Reloadable Uberjar'able App
|
||||
|
||||
This example lives in the `uberjar` branch. If you'd like to follow along:
|
||||
|
||||
```bash
|
||||
$ git checkout uberjar
|
||||
Switched to branch 'uberjar'
|
||||
```
|
||||
|
||||
### App state
|
||||
|
||||
Here is an example [app](https://github.com/tolitius/mount/tree/uberjar/test/app) that has these states:
|
||||
|
||||
```clojure
|
||||
16:20:44.997 [nREPL-worker-0] INFO mount - >> starting.. app-config
|
||||
16:20:44.998 [nREPL-worker-0] INFO mount - >> starting.. conn
|
||||
16:20:45.393 [nREPL-worker-0] INFO mount - >> starting.. nyse-app
|
||||
16:20:45.443 [nREPL-worker-0] INFO mount - >> starting.. nrepl
|
||||
```
|
||||
|
||||
where `nyse-app` is _the_ app. It has the usual routes:
|
||||
|
||||
```clojure
|
||||
(defroutes mount-example-routes
|
||||
|
||||
(GET "/" [] "welcome to mount sample app!")
|
||||
(GET "/nyse/orders/:ticker" [ticker]
|
||||
(generate-string (find-orders ticker)))
|
||||
|
||||
(POST "/nyse/orders" [ticker qty bid offer]
|
||||
(add-order ticker (bigdec bid) (bigdec offer) (Integer/parseInt qty))
|
||||
(generate-string {:added {:ticker ticker
|
||||
:qty qty
|
||||
:bid bid
|
||||
:offer offer}})))
|
||||
```
|
||||
|
||||
and the reloadable state:
|
||||
|
||||
```clojure
|
||||
(defn start-nyse []
|
||||
(create-nyse-schema) ;; creating schema (usually done long before the app is started..)
|
||||
(-> (routes mount-example-routes)
|
||||
(handler/site)
|
||||
(run-jetty {:join? false
|
||||
:port (get-in app-config [:www :port])})))
|
||||
|
||||
(defstate nyse-app :start (start-nyse)
|
||||
:stop (.stop nyse-app)) ;; it's a "org.eclipse.jetty.server.Server" at this point
|
||||
```
|
||||
|
||||
In order not to block, and being reloadable, the Jetty server is started in the "`:join? false`" mode which starts the server,
|
||||
and just returns a reference to it, so it can be easily stopped by `(.stop server)`
|
||||
|
||||
### "Uberjar is the :main"
|
||||
|
||||
In order for a standalone jar to run, it needs an entry point. This sample app [has one](https://github.com/tolitius/mount/blob/uberjar/test/app/app.clj#L16):
|
||||
|
||||
```clojure
|
||||
;; example of an app entry point
|
||||
(defn -main [& args]
|
||||
(mount/start))
|
||||
```
|
||||
|
||||
And some usual suspects from `project.clj`:
|
||||
|
||||
```clojure
|
||||
;; "test" is in sources here to just "demo" the uberjar without poluting mount "src"
|
||||
:uberjar {:source-paths ["test/app"]
|
||||
:dependencies [[compojure "1.4.0"]
|
||||
[ring/ring-jetty-adapter "1.1.0"]
|
||||
[cheshire "5.5.0"]
|
||||
[org.clojure/tools.nrepl "0.2.11"]
|
||||
[com.datomic/datomic-free "0.9.5327" :exclusions [joda-time]]]
|
||||
:main app
|
||||
:aot :all}}
|
||||
```
|
||||
|
||||
### REPL time
|
||||
|
||||
```clojure
|
||||
$ lein do clean, repl
|
||||
|
||||
user=> (dev)(reset)
|
||||
16:20:44.997 [nREPL-worker-0] INFO mount - >> starting.. app-config
|
||||
16:20:44.998 [nREPL-worker-0] INFO mount - >> starting.. conn
|
||||
16:20:45.393 [nREPL-worker-0] INFO mount - >> starting.. nyse-app
|
||||
|
||||
16:20:45.442 [nREPL-worker-0] INFO o.e.jetty.server.AbstractConnector - Started SelectChannelConnector@0.0.0.0:53600
|
||||
|
||||
16:20:45.443 [nREPL-worker-0] INFO mount - >> starting.. nrepl
|
||||
:ready
|
||||
dev=>
|
||||
```
|
||||
|
||||
Jetty server is started and ready to roll. And everything is still reloadable:
|
||||
|
||||
```clojure
|
||||
dev=> (reset)
|
||||
16:44:16.625 [nREPL-worker-2] INFO mount - << stopping.. nrepl
|
||||
16:44:16.626 [nREPL-worker-2] INFO mount - << stopping.. nyse-app
|
||||
16:44:16.711 [nREPL-worker-2] INFO mount - << stopping.. conn
|
||||
16:44:16.713 [nREPL-worker-2] INFO mount - << stopping.. app-config
|
||||
|
||||
16:44:16.747 [nREPL-worker-2] INFO mount - >> starting.. app-config
|
||||
16:44:16.748 [nREPL-worker-2] INFO mount - >> starting.. conn
|
||||
16:44:16.773 [nREPL-worker-2] INFO mount - >> starting.. nyse-app
|
||||
|
||||
16:44:16.777 [nREPL-worker-2] INFO o.e.jetty.server.AbstractConnector - Started SelectChannelConnector@0.0.0.0:54476
|
||||
|
||||
16:44:16.778 [nREPL-worker-2] INFO mount - >> starting.. nrepl
|
||||
```
|
||||
|
||||
Notice the Jetty port difference between reloads: `53600` vs. `54476`. This is done on purpose via [config](https://github.com/tolitius/mount/blob/uberjar/test/resources/config.edn#L4):
|
||||
|
||||
```clojure
|
||||
:www {:port 0} ;; Jetty will randomly assign the available port (this is good for dev reloadability)
|
||||
```
|
||||
|
||||
This of course can be solidified for different env deployments. For example I like `4242` :)
|
||||
|
||||
### Packaging one super uber jar
|
||||
|
||||
```clojure
|
||||
$ lein do clean, uberjar
|
||||
...
|
||||
Created /Users/tolitius/1/fun/mount/target/mount-0.1.0-SNAPSHOT-standalone.jar ;; your version may vary
|
||||
```
|
||||
|
||||
Let's give it a spin:
|
||||
|
||||
```bash
|
||||
$ java -jar target/mount-0.1.0-SNAPSHOT-standalone.jar
|
||||
...
|
||||
16:51:35.586 [main] DEBUG o.e.j.u.component.AbstractLifeCycle - STARTED SelectChannelConnector@0.0.0.0:54728
|
||||
```
|
||||
|
||||
Up and running on port `:54728`:
|
||||
|
||||
<img src="img/welcome-uberjar.png" width="250">
|
||||
|
||||
See if we have any orders:
|
||||
|
||||
<img src="img/get-uberjar.png" width="400">
|
||||
|
||||
we don't. let's put something into Datomic:
|
||||
|
||||
```clojure
|
||||
$ curl -X POST -d "ticker=GOOG&qty=100&bid=665.51&offer=665.59" "http://localhost:54728/nyse/orders"
|
||||
{"added":{"ticker":"GOOG","qty":"100","bid":"665.51","offer":"665.59"}}
|
||||
```
|
||||
|
||||
now we should:
|
||||
|
||||
<img src="img/post-uberjar.png" width="400">
|
||||
|
||||
### Choices
|
||||
|
||||
There are multiple ways to start a web app. This above is the most straighforward one: start server / stop server.
|
||||
|
||||
But depending on the requirements / architecture, the app can also have an entry point to `(mount/start)`
|
||||
via something like [:ring :init](https://github.com/weavejester/lein-ring#general-options)). Or the `(mount/start)`
|
||||
can go into the handler function, etc.
|
||||
|
|
@ -35,16 +35,16 @@
|
|||
(validate lifecycle)
|
||||
(let [s-meta (cond-> {:mount-state mount-state
|
||||
:order (make-state-seq state)
|
||||
:start `(fn [] (~@start))
|
||||
:start `(fn [] ~start)
|
||||
:started? false}
|
||||
stop (assoc :stop `(fn [] (~@stop)))
|
||||
suspend (assoc :suspend `(fn [] (~@suspend)))
|
||||
resume (assoc :resume `(fn [] (~@resume))))]
|
||||
stop (assoc :stop `(fn [] (~stop)))
|
||||
suspend (assoc :suspend `(fn [] (~suspend)))
|
||||
resume (assoc :resume `(fn [] (~resume))))]
|
||||
`(defonce ~(with-meta state (merge (meta state) s-meta))
|
||||
(NotStartedState. ~(str state))))))
|
||||
|
||||
(defn- record! [{:keys [ns name]} f done]
|
||||
(let [state (f)]
|
||||
(let [state (trampoline f)]
|
||||
(swap! done conj (ns-resolve ns name))
|
||||
state))
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
(start-server :bind host :port port))
|
||||
|
||||
;; nREPL is just another simple state
|
||||
(defstate nrepl :start (start-nrepl (:nrepl app-config))
|
||||
:stop (stop-server nrepl))
|
||||
(defstate nrepl :start #(start-nrepl (:nrepl app-config))
|
||||
:stop #(stop-server nrepl))
|
||||
|
||||
;; example of an app entry point
|
||||
(defn -main [& args]
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@
|
|||
edn/read-string))
|
||||
|
||||
(defstate app-config
|
||||
:start (load-config "test/resources/config.edn"))
|
||||
:start #(load-config "test/resources/config.edn"))
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
(.release conn) ;; usually it's not released, here just to illustrate the access to connection on (stop)
|
||||
(d/delete-database uri)))
|
||||
|
||||
(defstate conn :start (new-connection app-config)
|
||||
:stop (disconnect app-config conn))
|
||||
(defstate conn :start #(new-connection app-config)
|
||||
:stop #(disconnect app-config conn))
|
||||
|
||||
;; datomic schema (staging as an example)
|
||||
(defn create-schema [conn]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
(ns app.www
|
||||
(:require [app.nyse :refer [add-order find-orders create-nyse-schema]]
|
||||
[app.config :refer [app-config]]
|
||||
[app.utils.logging :refer [with-logging-status]]
|
||||
[mount.core :refer [defstate]]
|
||||
[cheshire.core :refer [generate-string]]
|
||||
[compojure.core :refer [routes defroutes GET POST]]
|
||||
|
|
@ -23,13 +22,10 @@
|
|||
|
||||
(defn start-nyse [{:keys [www]}]
|
||||
(create-nyse-schema) ;; creating schema (usually done long before the app is started..)
|
||||
(with-logging-status) ;; enables demo logging
|
||||
(-> (routes mount-example-routes)
|
||||
(handler/site)
|
||||
(run-jetty {:join? false
|
||||
:port (:port www)})))
|
||||
|
||||
(declare nyse-app) ;; in case it needs to be accessed in "resume-nyse" (helping out Clojure compiler)
|
||||
|
||||
(defstate nyse-app :start (start-nyse app-config)
|
||||
:stop (.stop nyse-app)) ;; it's a "org.eclipse.jetty.server.Server" at this point
|
||||
(defstate nyse-app :start #(start-nyse app-config)
|
||||
:stop #(.stop nyse-app)) ;; it's a "org.eclipse.jetty.server.Server" at this point
|
||||
|
|
|
|||
Loading…
Reference in a new issue