From f22466536701fb2a2871eafecac1cc8be80116e4 Mon Sep 17 00:00:00 2001 From: didier amyot Date: Sat, 14 Jul 2018 20:22:00 -0400 Subject: [PATCH] Implemented middleware in a somewhat stateless fashion --- README.md | 33 ++++++----- resources/config.edn | 13 ++++- src/skirmish/cli/benchmark.clj | 2 +- src/skirmish/core.clj | 35 +++++++---- src/skirmish/exchange/core.clj | 23 +++++--- src/skirmish/exchange/exchange.clj | 26 ++++----- src/skirmish/middleware/core.clj | 81 +++----------------------- src/skirmish/rest/core.clj | 43 ++++---------- src/skirmish/rest/exchange.clj | 14 +++-- src/skirmish/util.clj | 4 ++ test/skirmish/middleware/test_core.clj | 9 +++ 11 files changed, 124 insertions(+), 159 deletions(-) diff --git a/README.md b/README.md index 84d484d..83d8c0f 100644 --- a/README.md +++ b/README.md @@ -6,29 +6,36 @@ A stock exchange simulator. The scope of this project is not yet defined but it allow end user(s?) to exchange securities via the common exchange rules. -## Installation -Okay, this should be easy but it is hard... +## Getting Started + +### Development + +In development, you need nothing else than [clojure](https://clojure.org/) +and [leiningen](https://leiningen.org/). The easiest way to install clojure is +to make sure you have java (8 and above) and install leiningen. + +### Production + +You will need plenty of things, the majority of them are yet to define. +But, a good kafka deployment will be required. Probably a database, +hopefully datomic. ### Configuration All configuration are set in the `resources/config.edn` file. -Configuration uses [aero](https://github.com/juxt/aero). +Configuration uses [aero](https://github.com/juxt/aero). The configuration +file should be self documented. ### Technologies #### Server -* clojure -* kafka +* [clojure](https://clojure.org/) for programming language +* [kafka](http://kafka.apache.org/) for message broker/database #### In the browser -* https://aftertheflood.com/projects/sparks/ -* http://metricsgraphicsjs.org/ - -## Usage - -FIXME: explanation - - $ java -jar book-0.1.0-standalone.jar [args] +* [clojure script](https://clojurescript.org/) for programming language +* [sparks](https://aftertheflood.com/projects/sparks/) for trivial charts +* [metricsgraphicsjs](http://metricsgraphicsjs.org/) for complex charts diff --git a/resources/config.edn b/resources/config.edn index 296a801..bf3716d 100644 --- a/resources/config.edn +++ b/resources/config.edn @@ -1,11 +1,18 @@ { + ;; :middleware :kafka - :kafka + :kafka ;; all the kafka configuration {:bootstrap.servers #or [#env KAFKA_BOOTSTRAP_SERVERS "localhost:9092"] :topics-prefix "skirmish-01-"} - :webserver ;; possible config from http-kit's run-server - {:port #long #or [#env PORT 8080] + :api-server ;; possible config from http-kit's run-server that will affect the api-server + {:port #long #or [#env API_SERVER_PORT 1602] + :ip "0.0.0.0" + :max-body 524288 ;; 512kb, small but enough + :max-ws 524288} + + :api-bookkeeper ;; possible config from http-kit's run-server that will affect the api-bookkeeper + {:port #long #or [#env API_SERVER_PORT 1603] :ip "0.0.0.0" :max-body 524288 ;; 512kb, small but enough :max-ws 524288} diff --git a/src/skirmish/cli/benchmark.clj b/src/skirmish/cli/benchmark.clj index a948b90..882e7b0 100644 --- a/src/skirmish/cli/benchmark.clj +++ b/src/skirmish/cli/benchmark.clj @@ -94,7 +94,7 @@ (println "status map: " (:status results)) (pp-durations (:durations results))) -(defn -main [config] +(defn -main [config exchange-apis] (let [benchmark-config (:benchmark config) requests (-> benchmark-config :number-of-requests-per-securities (make-requests (:securities config))) orders (create-orders-channel (:securities config)) diff --git a/src/skirmish/core.clj b/src/skirmish/core.clj index b479c9e..ef0d680 100644 --- a/src/skirmish/core.clj +++ b/src/skirmish/core.clj @@ -3,28 +3,43 @@ [skirmish.rest.core] [skirmish.cli.benchmark] [skirmish.exchange.core] + [skirmish.middleware.core :refer [create-exchange-apis-for]] [aero.core :refer (read-config)] [clojure.core.async :as async]) (:gen-class)) -(def apps {:api-server skirmish.rest.core/api-main - :exchange-core skirmish.exchange.core/-main ;; should be skirmish.exchange.core/api-main - :bookkeeper skirmish.rest.core/bookeeper-main - :benchmark skirmish.cli.benchmark/-main}) +(def targets ;; a target is a function that blocks the thread and takes two + ;; arguments: the config map and the "exchange-api" + {:api-server skirmish.rest.core/api-main + :exchange-core skirmish.exchange.core/-main ;; should be skirmish.exchange.core/api-main + :api-bookkeeper skirmish.rest.core/bookeeper-main}) + ;:benchmark skirmish.cli.benchmark/-main}) + +(defn run-all-targets-in-separate-threads [target-fns config exchange-apis] + (println "running multiple targets. This is okay in dev but horrible in production") + (println target-fns) + (let [threads (map #(async/thread (% config exchange-apis)) target-fns) + finished-threads (async/merge threads)];; need to lock here + (loop [finished-thread (async/ "config.edn" clojure.java.io/resource read-config) - xf (comp keyword? apps) - target-fns (sequence xf (rest argv))] + xf (comp (map keyword) (map targets) (filter some?)) + target-fns (sequence xf argv)] (if (seq target-fns) (run-all-targets target-fns config) (do + (println argv) (println "target" target-fns "not found") (println "all known targets are...") - (println (keys apps)))))) \ No newline at end of file + (println (keys targets)))))) \ No newline at end of file diff --git a/src/skirmish/exchange/core.clj b/src/skirmish/exchange/core.clj index 20244cf..0d3b770 100644 --- a/src/skirmish/exchange/core.clj +++ b/src/skirmish/exchange/core.clj @@ -1,17 +1,22 @@ (ns skirmish.exchange.core - (:require [skirmish.middleware.core :refer [create-exchange-apis-for]] + (:require [skirmish.util :refer [block-thread]] + [skirmish.middleware.core :refer [create-exchange-apis-for]] [skirmish.exchange.exchange :refer [create-exchange-for-security empty-book]] - [clojure.core.async :as async] [kinsky.async] [kinsky.client])) (defn create-exchange-for [exchange-api] (let [initial-book empty-book - x (:new-order-publisher exchange-api)] - (create-exchange-for-security initial-book x))) + new-order-publisher (:new-order-publisher exchange-api) + trades-submitter (:trades-submitter exchange-api) + orders-submitter (:orders-submitter exchange-api)] -(defn -main [config] - (let [exchange-apis (create-exchange-apis-for config)] - (doseq [{sec-id :sec-id} (:securities exchange-apis)] - (create-exchange-for (get exchange-apis sec-id))))) - ; ;api-routes (create-routes exchange-apis kafka-producer)]));; \ No newline at end of file + (create-exchange-for-security initial-book + new-order-publisher + trades-submitter + orders-submitter))) + +(defn -main [config exchange-apis] + (doseq [{sec-id :sec-id} (:securities config)] + (create-exchange-for (get exchange-apis sec-id))) + (block-thread)) \ No newline at end of file diff --git a/src/skirmish/exchange/exchange.clj b/src/skirmish/exchange/exchange.clj index f99934a..464fe01 100644 --- a/src/skirmish/exchange/exchange.clj +++ b/src/skirmish/exchange/exchange.clj @@ -79,22 +79,16 @@ ;; bellow are the really interesting functions. -(defn create-exchange-for-security [initial-book incoming-order-channel trades-channel outcoming-orders-channel] - ;; output - trades channel, pub channel - ;; - orders channel, pub channel - (let [trades-channel (chan) - outcoming-orders-channel (chan)] - (async/go - (loop [order (!! incoming-order-channel order) (build-response request order)) @@ -118,13 +118,19 @@ (chan-to-websocket request order-channel ws-chan))))) -(defn create-routes [exchange-apis] +(defn create-routes-for-exchange [exchange-apis] + (routes + (context "/api/1" [] + (POST "/securities/:sec-id" [] (create-order exchange-apis))) + (not-found "not found"))) + + +(defn create-routes-for-bookkeeper [exchange-apis] (routes (context "/api/1" [] (GET "/" [] api-description) (GET "/securities" [] (securities exchange-apis)) (GET "/securities/:sec-id" [] (get-security exchange-apis)) - (POST "/securities/:sec-id" [] (create-order exchange-apis)) (GET "/securities/:sec-id/trades" [] (listen-to-trades exchange-apis)) (GET "/securities/:sec-id/orders" [] (listen-to-orders exchange-apis))) (not-found "not found"))) diff --git a/src/skirmish/util.clj b/src/skirmish/util.clj index 18483f5..995a47c 100644 --- a/src/skirmish/util.clj +++ b/src/skirmish/util.clj @@ -1,4 +1,5 @@ (ns skirmish.util + (:require [clojure.core.async :as async]) (:import [skirmish.PersistentSortedList])) (defn sorted-list [& keys] @@ -7,3 +8,6 @@ (defn sorted-list-by [comparator & keys] (skirmish.PersistentSortedList/create comparator (seq keys))) +(defn block-thread [] + "Block the current thread until chuck norris gets weaker." + (async/ [:molly :pepper] + (vals exchanges) => #(every? map? %)))) \ No newline at end of file