Skip to content

Commit

Permalink
service architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
awb99 committed Jun 25, 2024
1 parent 7fdc4f5 commit 289f86f
Show file tree
Hide file tree
Showing 18 changed files with 205 additions and 273 deletions.
1 change: 0 additions & 1 deletion build.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
(ns build
(:require
[babashka.fs :as fs]
[clojure.tools.build.api :as b]
[deps-deploy.deps-deploy :as dd]))

Expand Down
26 changes: 20 additions & 6 deletions demo/resources/demoservices.edn
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,28 @@

:timbre {:start (modular.log/timbre-config!
(:timbre/clj (deref (clip/ref :config))))}

:webly {:start (webly.app.app/start-webly
(deref (clip/ref :config))
(deref (clip/ref :config))
(:profile #ref [:modular]))
:stop (webly.app.app/stop-webly this)}

:clj-service {:start (goldly.service/start-clj-services
(clip/ref :exts))}
:stop (webly.app.app/stop-webly this)}

:permission {:start (modular.permission.core/start-permissions
{:demo {:roles #{:logistic}
:password "a231498f6c1f441aa98482ea0b224ffa" ; "1234"
:email ["[email protected]"]}
:boss {:roles #{:logistic :supervisor :accounting}
:password "a231498f6c1f441aa98482ea0b224ffa" ; "1234"
:email ["[email protected]"]}
:florian {:roles #{:logistic}
:password "a231498f6c1f441aa98482ea0b224ffa" ; 1234
:email ["[email protected]"]}
:john {:roles #{:logistic}
:password "a231498f6c1f441aa98482ea0b224ffa" ; "1234"
:email ["[email protected]"]}})}


:clj-service {:start (clj-service.core/start-clj-services (clip/ref :permission) (clip/ref :exts))}

;
}}
Expand Down
5 changes: 2 additions & 3 deletions demo/resources/ext/demo.edn
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
:cljs-namespace [demo.page]
:cljs-ns-bindings {'demo.page {'service-page demo.page/service-page}}
;runtime
:cljs-routes {"" demo.page/service-page}
:clj-services {:name "demo"
:permission #{}
:symbols [demo.service/add
demo.service/quote
demo.service/quote-slow
demo.service/ex]}

:cljs-routes {"" demo.page/service-page}}
demo.service/ex]}}

2 changes: 1 addition & 1 deletion demo/src/demo/page.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
(:require
[reagent.core :as r]
[promesa.core :as p]
[goldly.service.core :refer [clj clj-atom run-a run-a-map]]))
[goldly.service.core :refer [clj]]))

(def state
(r/atom {}))
Expand Down
5 changes: 1 addition & 4 deletions demo/src/demo/service.clj
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
(ns demo.service
(:require
[taoensso.timbre :as timbre :refer [debug]]))
(ns demo.service)

(debug "namespace demo.service is getting loaded...")

(defn add [a b]
(+ a b))
Expand Down
11 changes: 4 additions & 7 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@
"resources"]
:deps
{org.clojure/clojure {:mvn/version "1.11.1"}
org.clojure/core.async {:mvn/version "1.6.673"}
org.clojure/data.json {:mvn/version "2.4.0"}
com.rpl/specter {:mvn/version "1.1.4"}
org.pinkgorilla/websocket {:mvn/version "0.0.7"}
org.pinkgorilla/permission {:mvn/version "0.0.15"}
org.pinkgorilla/ui-dialog-keybindings {:mvn/version "0.1.6"}
;org.clojure/data.json {:mvn/version "2.4.0"}
org.pinkgorilla/timbre {:mvn/version "0.0.6"}
org.pinkgorilla/extension {:mvn/version "0.0.12"}
}
org.pinkgorilla/permission {:mvn/version "0.2.18"}
org.pinkgorilla/websocket {:mvn/version "0.0.11"}}
:aliases
{; github ci
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.9.6"}
Expand Down
9 changes: 3 additions & 6 deletions resources/ext/clj-service.edn
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,12 @@
:cljs-namespace [goldly.service.core]
:cljs-ns-bindings {'goldly.service.core {'clj goldly.service.core/clj
'clj-atom goldly.service.core/clj-atom
'run goldly.service.core/run
'run-a goldly.service.core/run-a
'run-a-map goldly.service.core/run-a-map
'run-cb goldly.service.core/run-cb
'wait-chan-result goldly.service.core/wait-chan-result}}
'run-cb goldly.service.core/run-cb ; depreciate ??
}}
; run
; clj-service allows requests via http post
:api-routes {"clj-service" {:post goldly.service.handler/service-handler-wrapped}}
:clj-services {:name "clj-service-dissovery"
:clj-services {:name "clj-service-discovery"
:permission #{}
:symbols [goldly.service/services-list]}
;
Expand Down
88 changes: 88 additions & 0 deletions src/clj_service/core.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
(ns clj-service.core
(:require
[clojure.set :refer [rename-keys]]
[taoensso.timbre :as timbre :refer [info error]]
[extension :refer [write-target-webly get-extensions]]
[modular.permission.service :refer [add-permissioned-service]]
[modular.clj-service.websocket :refer [create-websocket-responder]]))

;; EXPOSE FUNCTION

(defn- resolve-symbol [s]
(try
(requiring-resolve s)
(catch Exception ex
(error "Exception in exposing service " s " - symbol cannot be required.")
(throw ex))))

(defn expose-function
"exposes one function
services args: this - created via clj-service.core
permission-service - created via modular.permission.core/start-permissions
function args: service - fully qualified symbol
permission - a set following modular.permission role based access
fixed-args - fixed args to be passed to the function executor as the beginning arguments"
[this permission-service {:keys [function permission fixed-args]
:or {fixed-args []
permission nil}}]
(assert this "you need to pass the clj-service state")
(assert permission-service "you need to pass the modular.permission.core state")
(assert (symbol? function))
(let [service-fn (resolve-symbol function)]
(add-permissioned-service permission-service function permission)
(swap! this assoc function {:service-fn service-fn
:permission permission
:fixed-args fixed-args})))

(defn expose-functions
"exposes multiple functions with the same permission and fixed-args."
[this permission-service
{:keys [function-symbols permission fixed-args name]
:or {permission nil
fixed-args []
name "services"}}]
(assert (vector? function-symbols))
(info "exposing [" name "] permission: " permission " functions: " function-symbols)
(doall
(map (fn [function]
(expose-function this permission-service {:function function
:permission permission
:fixed-args fixed-args})) function-symbols)))

; services list

(defn services-list [this]
(keys @this))

; start service

(defn- exts->services [exts]
(->> (get-extensions exts {:clj-services nil})
(map :clj-services)
(remove nil?)))

(defn start-clj-services
"starts the clj-service service.
exposes stateless services that are discovered via the extension system.
non stateless services need to be exposed via expose-service"
[permission-service exts]
(info "starting clj-services ..")
(let [this (atom {})
services (exts->services exts)]
(write-target-webly :clj-services services)
; expose services list (which is stateful)
(expose-function this permission-service
{:service-fn 'clj-service.core/services-list
:permission nil
:fixed-args [this]})
; expose stateless services discovered via extension-manager
(doall
(map (fn [clj-service]
(expose-functions
this permission-service
(rename-keys clj-service {:symbols :function-symbols})))
services))
; create websocket message handler
(create-websocket-responder this permission-service)
; return the service state
this))
35 changes: 35 additions & 0 deletions src/clj_service/executor.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
(ns clj-service.executor
(:require
[de.otto.nom.core :as nom]
[modular.permission.core :refer [user-authorized?]]))

;; USER

(defonce ^:dynamic
^{:doc "The user-id for which a clj-service gets executed"}
*user* nil)

(defn execute [this permission-service user-id fun-symbol & args]
(if-let [fun (get this fun-symbol)]
(let [{:keys [function _permission fixed-args]} fun
full-args (concat fixed-args args)]
(if (user-authorized? permission-service fun-symbol user-id)
(try {:result (apply function full-args)}
(catch clojure.lang.ExceptionInfo e
(nom/fail ::exception {:fun fun-symbol
:error (pr-str e)
:message (str "exception when executing function " fun-symbol)}))
(catch AssertionError e
(nom/fail ::assert-error {:fun fun-symbol
:error (pr-str e)
:message (str "assert error when executing function " fun-symbol)}))
(catch Exception e
(nom/fail ::exception {:fun fun-symbol
:error (pr-str e)
:message (str "exception when executing function " fun-symbol)})))
(nom/fail ::no-permission {:fun fun-symbol
:user user-id
:message (str "user-id " user-id " is not authorized for: " fun-symbol)})))
(nom/fail ::function-not-found {:fun fun-symbol
:message "No service defined for this symbol."})))

12 changes: 7 additions & 5 deletions src/goldly/service/handler.clj → src/clj_service/handler.clj
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
(ns goldly.service.handler
(ns clj-service.handler
(:require
[taoensso.timbre :refer [trace debug info error]]
[taoensso.timbre :refer [info]]
[ring.util.response :as res]
[de.otto.nom.core :as nom]
[modular.webserver.middleware.api :refer [wrap-api-handler]]
[goldly.service.core :refer [run-service]]))
[goldly.service.executor :refer [run-service *user*]]))

(defn service-handler
[req]
(info "service-api-handler: " req)
(let [body-params (:body-params req)
args (select-keys body-params [:fun :args])
_ (info "service: " args)
response (run-service args)]
(if (:error response)
response (binding [*user* nil]
(run-service args))]
(if (nom/anomaly? response)
(res/bad-request response)
(res/response response))))

Expand Down
22 changes: 22 additions & 0 deletions src/clj_service/websocket.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
(ns clj-service.websocket
(:require
[clojure.string]
[de.otto.nom.core :as nom]
[modular.ws.core :refer [send-response]]
[modular.ws.msg-handler :refer [-event-msg-handler]]
[modular.permission.session :refer [get-user]]
[clj-service.executor :refer [execute *user*]]))

(defn create-websocket-responder [this permission-service]
(defmethod -event-msg-handler :clj/service
[{:keys [event _id _?data uid] :as req}]
(let [[_ params] event ; _ is :clj/service
{:keys [fun args]} params
user (get-user permission-service uid)]
(future
(let [r (binding [*user* user]
(execute this permission-service user fun args))]
(if (nom/anomaly? r)
(send-response req :clj/service {:error "Execution exception"
:uid uid})
(send-response req :clj/service {:result r})))))))
20 changes: 0 additions & 20 deletions src/goldly/service.clj

This file was deleted.

65 changes: 0 additions & 65 deletions src/goldly/service/core.clj

This file was deleted.

Loading

0 comments on commit 289f86f

Please sign in to comment.