Skip to content

Commit 0088e35

Browse files
committed
Initial release
Signed-off-by: Greg Haskins <[email protected]>
1 parent e4671b5 commit 0088e35

22 files changed

+819
-3
lines changed

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/target
2+
/classes
3+
/checkouts
4+
profiles.clj
5+
pom.xml
6+
pom.xml.asc
7+
*.jar
8+
*.class
9+
/.lein-*
10+
/.nrepl-port
11+
/.prepl-port
12+
.hgignore
13+
.hg/

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,4 +198,4 @@
198198
distributed under the License is distributed on an "AS IS" BASIS,
199199
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200200
See the License for the specific language governing permissions and
201-
limitations under the License.
201+
limitations under the License.

README.md

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,47 @@
1-
# temporal-clojure-sdk
2-
A Temporal SDK for Clojure
1+
# Temporal Clojure SDK
2+
3+
[Temporal](https://github.com/temporalio/temporal) is a Workflow-as-Code platform for building and operating
4+
resilient applications using developer-friendly primitives, instead of constantly fighting your infrastructure.
5+
6+
This Clojure SDK is a framework for authoring Workflows and Activities in Clojure. (For other languages, see [Temporal SDKs](https://docs.temporal.io/application-development).)
7+
8+
- [Temporal docs](https://docs.temporal.io/)
9+
- [Install Temporal Server](https://docs.temporal.io/docs/server/quick-install)
10+
- [Temporal CLI](https://docs.temporal.io/docs/devtools/tctl/)
11+
12+
## Requirements
13+
14+
- JDK 1.8+
15+
16+
## macOS Users
17+
18+
Due to issues with default hostname resolution
19+
(see [this StackOverflow question](https://stackoverflow.com/questions/33289695/inetaddress-getlocalhost-slow-to-run-30-seconds) for more details),
20+
macOS Users may see gRPC `DEADLINE_EXCEEDED` errors and other slowdowns when running the SDK.
21+
22+
To solve the problem add the following entries to your `/etc/hosts` file (where my-macbook is your hostname):
23+
24+
```conf
25+
127.0.0.1 my-macbook
26+
::1 my-macbook
27+
```
28+
29+
## Contributing
30+
31+
Pull requests welcome. Please be sure to include a [DCO](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin) in any commit messages.
32+
33+
## License
34+
35+
Copyright (C) 2022 Manetu, Inc. All Rights Reserved.
36+
37+
Licensed under the Apache License, Version 2.0 (the "License");
38+
you may not use this material except in compliance with the License.
39+
You may obtain a copy of the License at
40+
41+
http://www.apache.org/licenses/LICENSE-2.0
42+
43+
Unless required by applicable law or agreed to in writing, software
44+
distributed under the License is distributed on an "AS IS" BASIS,
45+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
46+
See the License for the specific language governing permissions and
47+
limitations under the License.

dev-resources/user.clj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
;; Copyright © 2022 Manetu, Inc. All rights reserved
2+
3+
(ns user
4+
(:require [clojure.tools.namespace.repl :refer [refresh]]))
5+

doc/intro.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Introduction
2+
3+
[Temporal](https://github.com/temporalio/temporal) is a Workflow-as-Code platform for building and operating
4+
resilient applications using developer-friendly primitives, instead of constantly fighting your infrastructure.
5+
6+
The Clojure SDK is the framework for authoring Workflows and Activities in Clojure. (For other languages, see [Temporal SDKs](https://docs.temporal.io/application-development).)
7+
8+
- [Temporal docs](https://docs.temporal.io/)
9+
- [Install Temporal Server](https://docs.temporal.io/docs/server/quick-install)
10+
- [Temporal CLI](https://docs.temporal.io/docs/devtools/tctl/)

project.clj

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
(defproject io.github.manetu/temporal-sdk "0.1.0-SNAPSHOT"
2+
:description "A Temporal SDK for Clojure"
3+
:url "https://github.com/manetu/temporal-clojure-sdk"
4+
:license {:name "Apache License 2.0"
5+
:url "https://www.apache.org/licenses/LICENSE-2.0"
6+
:year 2022
7+
:key "apache-2.0"}
8+
:plugins [[lein-cljfmt "0.8.2"]
9+
[lein-kibit "0.1.8"]
10+
[lein-bikeshed "0.5.2"]
11+
[lein-cloverage "1.2.4"]
12+
[jonase/eastwood "1.2.4"]
13+
[lein-codox "0.10.8"]]
14+
:dependencies [[org.clojure/clojure "1.11.1"]
15+
[io.temporal/temporal-sdk "1.16.0"]
16+
[io.temporal/temporal-testing "1.16.0"]
17+
[com.taoensso/timbre "5.2.1"]
18+
[funcool/promesa "8.0.450"]]
19+
:repl-options {:init-ns user}
20+
:aot [temporal.internal.dispatcher]
21+
22+
:eastwood {:add-linters [:unused-namespaces]}
23+
:codox {:metadata {:doc/format :markdown}}
24+
25+
:profiles {:dev {:dependencies [[org.clojure/tools.namespace "1.3.0"]
26+
[eftest "0.5.9"]]}}
27+
:cloverage {:runner :eftest
28+
:runner-opts {:multithread? false
29+
:fail-fast? true}
30+
:fail-threshold 92
31+
:ns-exclude-regex [#"temporal.client.worker"]})

src/temporal/activity.clj

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
;; Copyright © 2022 Manetu, Inc. All rights reserved
2+
3+
(ns temporal.activity
4+
"Methods for defining and invoking activity tasks"
5+
(:require [clojure.edn :as edn]
6+
[taoensso.timbre :as log]
7+
[promesa.core :as p]
8+
[temporal.internal.activity :as a]
9+
[temporal.internal.utils :refer [->promise] :as u])
10+
(:import [io.temporal.workflow Workflow]
11+
[io.temporal.activity ActivityOptions ActivityOptions$Builder]
12+
[java.time Duration]))
13+
14+
(def ^:no-doc invoke-option-spec
15+
{:start-to-close-timeout #(.setStartToCloseTimeout ^ActivityOptions$Builder %1 %2)})
16+
17+
(defn- invoke-options->
18+
^ActivityOptions [params]
19+
(u/build (ActivityOptions/newBuilder) invoke-option-spec params))
20+
21+
(def ^:no-doc default-invoke-options {:start-to-close-timeout (Duration/ofSeconds 3)})
22+
23+
(defn invoke
24+
"
25+
Invokes 'activity' with 'params' from within a workflow context. Returns a promise that when derefed will resolve to
26+
the evaluation of the defactivity once the activity concludes.
27+
28+
```clojure
29+
(defactivity my-activity
30+
[ctx {:keys [foo] :as args}]
31+
...)
32+
33+
(invoke my-activity {:foo \"bar\"} {:start-to-close-timeout (Duration/ofSeconds 3))
34+
```
35+
"
36+
([activity params] (invoke activity params default-invoke-options))
37+
([activity params options]
38+
(let [act-name (a/get-annotation activity)
39+
stub (Workflow/newUntypedActivityStub (invoke-options-> options))]
40+
(-> (->promise
41+
(.executeAsync stub act-name String (u/->objarray params)))
42+
(p/then edn/read-string)))))
43+
44+
(defmacro defactivity
45+
"
46+
Defines a new activity, similar to defn, expecting a 2-arity parameter list and body. Should evaluate to something
47+
serializable with [prn-str](https://clojuredocs.org/clojure.core/prn-str), which will be available to the [[invoke]] caller.
48+
49+
Arguments:
50+
51+
- `ctx`: Context passed through from [[temporal.client.worker/start]]
52+
- `args`: Passed from 'params' of [[invoke]]
53+
54+
```clojure
55+
(defactivity greet-activity
56+
[ctx {:keys [name] :as args}]
57+
(str \"Hi, \" name))
58+
```
59+
"
60+
[name params* & body]
61+
(let [class-name (u/get-classname name)]
62+
`(def ~name ^{::a/def ~class-name}
63+
(fn [ctx# args#]
64+
(log/trace (str ~class-name ": ") args#)
65+
(let [f# (fn ~params* (do ~@body))]
66+
(f# ctx# args#))))))

src/temporal/client/core.clj

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
;; Copyright © 2022 Manetu, Inc. All rights reserved
2+
3+
(ns temporal.client.core
4+
"Methods for client interaction with Temporal"
5+
(:require [clojure.edn :as edn]
6+
[promesa.core :as p]
7+
[temporal.internal.workflow :as w]
8+
[temporal.internal.utils :as u])
9+
(:import [io.temporal.client WorkflowClient WorkflowStub WorkflowOptions WorkflowOptions$Builder]
10+
[io.temporal.serviceclient WorkflowServiceStubs]))
11+
12+
(defn create-client
13+
"
14+
Creates a new client instance suitable for implementing Temporal workers (See [[temporal.client.worker/start]]) or
15+
workflow clients (See [[create-workflow]]).
16+
"
17+
[]
18+
(let [service (WorkflowServiceStubs/newLocalServiceStubs)]
19+
(WorkflowClient/newInstance service)))
20+
21+
(def ^:no-doc wf-option-spec
22+
{:task-queue #(.setTaskQueue ^WorkflowOptions$Builder %1 (u/namify %2))
23+
:workflow-id #(.setWorkflowId ^WorkflowOptions$Builder %1 %2)})
24+
25+
(defn- wf-options->
26+
^WorkflowOptions [params]
27+
(u/build (WorkflowOptions/newBuilder) wf-option-spec params))
28+
29+
(defn create-workflow
30+
"
31+
Create a new workflow-stub instance, suitable for managing and interacting with a workflow through it's lifecycle.
32+
33+
*N.B.: The workflow will remain in an uninitialized and idle state until explicitly started with either ([[start]]) or
34+
([[signal-with-start]]).*
35+
36+
```clojure
37+
(defworkflow my-workflow
38+
[ctx args]
39+
...)
40+
41+
(let [w (create client my-workflow {:task-queue ::my-task-queue})]
42+
;; do something with the instance 'w')
43+
```
44+
"
45+
[^WorkflowClient client workflow options]
46+
(let [wf-name (w/get-annotation workflow)
47+
stub (.newUntypedWorkflowStub client wf-name (wf-options-> options))]
48+
{:client client :stub stub}))
49+
50+
(defn start
51+
"
52+
Starts 'worklow' with 'params'"
53+
[{:keys [^WorkflowStub stub] :as workflow} params]
54+
(.start stub (u/->objarray params)))
55+
56+
(defn signal-with-start
57+
"
58+
Signals 'workflow' with 'signal-params' on signal 'signal-name', starting it if not already running. 'wf-params' are
59+
used as workflow start arguments if the workflow needs to be started"
60+
[{:keys [^WorkflowStub stub] :as workflow} signal-name signal-params wf-params]
61+
(.signalWithStart stub (u/namify signal-name) (u/->objarray signal-params) (u/->objarray wf-params)))
62+
63+
(defn >!
64+
"
65+
Sends 'params' as a signal 'signal-name' to 'workflow'
66+
67+
```clojure
68+
(>! workflow ::my-signal {:msg \"Hi\"})
69+
```
70+
"
71+
[{:keys [^WorkflowStub stub] :as workflow} signal-name params]
72+
(.signal stub (u/namify signal-name) (u/->objarray params)))
73+
74+
(defn get-result
75+
"
76+
Retrieves the final result of 'workflow'. Returns a promise that when derefed will resolve to the evaluation of the
77+
defworkflow once the workflow concludes.
78+
79+
```clojure
80+
(defworkflow my-workflow
81+
[ctx args]
82+
...)
83+
84+
(let [w (create ...)]
85+
(start w ...)
86+
@(get-result w))
87+
```
88+
"
89+
[{:keys [^WorkflowStub stub] :as workflow}]
90+
(-> (.getResultAsync stub String)
91+
(p/then edn/read-string)))
92+
93+
(defn cancel
94+
"
95+
Gracefully cancels 'workflow'
96+
97+
```clojure
98+
(cancel workflow)
99+
```
100+
"
101+
[{:keys [^WorkflowStub stub] :as workflow}]
102+
(.cancel stub))
103+
104+
(defn terminate
105+
"
106+
Forcefully terminates 'workflow'
107+
108+
```clojure
109+
(terminate workflow \"unresponsive\", {})
110+
```
111+
"
112+
[{:keys [^WorkflowStub stub] :as workflow} reason params]
113+
(.terminate stub reason (u/->objarray params)))

src/temporal/client/worker.clj

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
;; Copyright © 2022 Manetu, Inc. All rights reserved
2+
3+
(ns temporal.client.worker
4+
"Methods for managing a Temporal worker instance"
5+
(:require [temporal.internal.activity :as a]
6+
[temporal.internal.utils :as u])
7+
(:import [io.temporal.worker Worker WorkerFactory]
8+
[temporal.internal.dispatcher WorkflowImpl]))
9+
10+
(defn ^:no-doc init
11+
"
12+
Initializes a worker instance, suitable for real connections or unit-testing with temporal.testing.env
13+
"
14+
[^Worker worker ctx]
15+
(.addWorkflowImplementationFactory worker WorkflowImpl (u/->Func (fn [] (new WorkflowImpl ctx))))
16+
(.registerActivitiesImplementations worker (to-array [(a/dispatcher ctx)])))
17+
18+
(defn start
19+
"
20+
Starts a worker processing loop.
21+
22+
Arguments:
23+
24+
- `client`: WorkflowClient instance returned from [[temporal.client.core/create-client]]
25+
- `queue-name`: The name of the task-queue for this worker instance to listen on. Accepts a string or fully-qualified
26+
keyword.
27+
- `ctx`: (optional) an opaque handle that is passed as the first argument of [[temporal.workflow/defworkflow]]
28+
and [[temporal.activity/defactivity]]. Useful for passing state such as database or network
29+
connections. Not interpreted in any manner.
30+
31+
```clojure
32+
(start ::my-queue {:some \"context\"})
33+
```
34+
"
35+
([client queue-name] (start client queue-name nil))
36+
([client queue-name ctx]
37+
(let [factory (WorkerFactory/newInstance client)
38+
worker (.newWorker factory (u/namify queue-name))]
39+
(init worker ctx)
40+
(.start factory)
41+
{:factory factory :worker worker})))
42+
43+
(defn stop
44+
"
45+
Stops a running worker.
46+
47+
Arguments:
48+
49+
- `instance`: Result returned from original call to ([[start]])
50+
51+
```clojure
52+
(let [instance (start ::my-queue)]
53+
...
54+
(stop instance))
55+
```
56+
"
57+
[{:keys [^WorkerFactory factory] :as instance}]
58+
(.shutdown factory))

src/temporal/core.clj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
;; Copyright © 2022 Manetu, Inc. All rights reserved
2+
3+
(ns temporal.core
4+
(:import [io.temporal.workflow Workflow]
5+
[java.util.function Supplier]))
6+
7+
(defn await
8+
"Efficiently parks the workflow until 'pred' evaluates to true. Re-evaluates on each state transition"
9+
[pred]
10+
(Workflow/await
11+
(reify Supplier
12+
(get [_]
13+
(pred)))))

0 commit comments

Comments
 (0)