Skip to content

Commit ff2b404

Browse files
committed
Documentation updates
Signed-off-by: Greg Haskins <[email protected]>
1 parent a2afb90 commit ff2b404

File tree

4 files changed

+234
-15
lines changed

4 files changed

+234
-15
lines changed

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,19 @@ resilient applications using developer-friendly primitives, instead of constantl
77

88
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).)
99

10+
### Clojure SDK
11+
12+
- [Clojure SDK and API documentation](https://cljdoc.org/d/io.github.manetu/temporal-sdk)
13+
14+
### Temporal in general
15+
1016
- [Temporal docs](https://docs.temporal.io/)
1117
- [Install Temporal Server](https://docs.temporal.io/docs/server/quick-install)
1218
- [Temporal CLI](https://docs.temporal.io/docs/devtools/tctl/)
1319

1420
## Requirements
1521

16-
- JDK 1.8+
22+
- JDK 11+
1723

1824
## macOS Users
1925

@@ -28,10 +34,6 @@ To solve the problem add the following entries to your `/etc/hosts` file (where
2834
::1 my-macbook
2935
```
3036

31-
## Using
32-
33-
You can peruse the [API Doc](https://cljdoc.org/d/io.github.manetu/temporal-sdk)
34-
3537
## Contributing
3638

3739
Pull requests welcome. Please be sure to include a [DCO](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin) in any commit messages.

doc/activities.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Activities
2+
3+
## What is an Activity?
4+
5+
Activities are implementations of certain tasks which need to be performed during a Workflow execution. They can be used to interact with external systems, such as databases, services, etc.
6+
7+
Workflows orchestrate invocations of Activities.
8+
9+
## Implementing Activities
10+
11+
An Activity implementation consists of defining a (defactivity) function. This function is invoked by the platform each time an Activity execution is started or retried. As soon as this method returns, the Activity execution is considered as completed and the result is available to the caller.
12+
13+
### Example
14+
15+
```clojure
16+
(require '[temporal.activity :refer [defactivity] :as a])
17+
18+
(defactivity greet-activity
19+
[ctx {:keys [name] :as args}]
20+
(str "Hi, " name))
21+
```
22+
23+
## Registering Activities
24+
25+
By default, Activities are automatically registered simply by declaring a (defactivity). You may optionally manually declare specific Activities to register when creating Workers (see [worker-options](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.client.worker#worker-options)).
26+
27+
*It should be noted that the name of the Activity is part of a contract, along with the arguments that the Activity accepts. Therefore, the Activity definition must be treated with care whenever code is refactored.*
28+
29+
## Starting Activity Executions
30+
31+
In this Clojure SDK, Activities are always started with either [invoke](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.activity#invoke) or [local-invoke](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.activity#local-invoke), both of which are called similarly. The primary difference between them is the execution model under the covers (See [What is a Local Activity](https://docs.temporal.io/concepts/what-is-a-local-activity/))
32+
33+
### Example
34+
35+
```clojure
36+
(require '[temporal.activity :refer [defactivity] :as a])
37+
38+
(defactivity my-activity
39+
[ctx {:keys [foo] :as args}]
40+
...)
41+
42+
(a/invoke my-activity {:foo "bar"})
43+
```
44+
45+
## Asynchronous Mode
46+
47+
Returning a core.async channel places the activity into [Asynchronous mode](https://docs.temporal.io/java/activities/#asynchronous-activity-completion), where the result may be resolved at a future time by sending a single message on the channel. Sending a [Throwable](https://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html) will signal a failure of the activity. Any other value will be serialized and returned to the caller.
48+
49+
### Example
50+
51+
```clojure
52+
(require '[temporal.activity :refer [defactivity] :as a])
53+
(require '[clojure.core.async :refer [go <!] :as async])
54+
55+
(defactivity async-greet-activity
56+
[ctx {:keys [name] :as args}]
57+
(go
58+
(<! (async/timeout 1000))
59+
(str "Hi, " name)))
60+
```

doc/intro.md

Lines changed: 0 additions & 10 deletions
This file was deleted.

doc/workflows.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Workflows
2+
3+
## What is a Workflow?
4+
5+
Workflows are resilient programs, meaning that they will continue execution even in the presence of different failure conditions.
6+
7+
Workflows encapsulate execution/orchestration of Tasks which include Activities and child Workflows. They also need to react to external events, deal with Timeouts, etc.
8+
9+
In this Clojure SDK programming model, a Temporal Workflow is a function declared with ([defworkflow](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.workflow#defworkflow))
10+
11+
```clojure
12+
(defworkflow my-workflow
13+
[ctx params]
14+
...)
15+
```
16+
17+
## Implementing Workflows
18+
19+
A Workflow implementation consists of defining a (defworkflow) function. This function is invoked by the platform each time a new Workflow execution is started or retried. As soon as this method returns, the Workflow execution is considered as completed and the result is available to the caller via ([get-result](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.client.core#get-result)).
20+
21+
### Example
22+
23+
```clojure
24+
(require '[temporal.workflow :refer [defworkflow]])
25+
26+
(defworkflow my-workflow
27+
[ctx {{:keys [foo]} :args}]
28+
...)
29+
```
30+
31+
### Workflow Implementation Constraints
32+
33+
Temporal uses the [Event Sourcing pattern](https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing) to recover the state of a Workflow object including its threads and local variable values. In essence, every time a Workflow state has to be restored, its code is re-executed from the beginning. Note that during replay, successfully executed Activities are not re-executed as their results are already recorded in the Workflow event history.
34+
35+
Even though Temporal has the replay capability, which brings resilience to your Workflows, you should never think about this capability when writing your Workflows. Instead, you should focus on implementing your business logic/requirements and write your Workflows as they would execute only once.
36+
37+
There are some things however to think about when writing your Workflows, namely determinism and isolation. We summarize these constraints here:
38+
39+
- Do not use any mutable global variables such as atoms in your Workflow implementations. This will assure that multiple Workflow instances are fully isolated.
40+
- Do not call any non-deterministic functions like non-seeded random or uuid-generators directly from the Workflow code. (Coming soon: SideEffect API)
41+
- Perform all IO operations and calls to third-party services on Activities and not Workflows, as they are usually non-deterministic in nature.
42+
- Do not use any programming language constructs that rely on system time. (Coming soon: API methods for time)
43+
- Do not use threading primitives such as clojure.core.async/go or clojure.core.async/thread. (Coming soon: API methods for async function execution)
44+
- Do not perform any operations that may block the underlying thread, such as clojure.core.async/<!!.
45+
- There is no general need in explicit synchronization because multi-threaded code inside a Workflow is executed one thread at a time and under a global lock.
46+
- This Clojure SDK provides integration with [promesa](https://github.com/funcool/promesa) with a few limitations (See [Promises](#promises)), for asynchronous integration with safe blocking operations, such as waiting on an Activity.
47+
- (Coming soon) Use versioning-support when making any changes to the Workflow code. Without this, any deployment of updated Workflow code might break already running Workflows.
48+
- Don’t access configuration APIs directly from a Workflow because changes in the configuration might affect a Workflow execution path. Pass it as an argument to a Workflow function or use an Activity to load it.
49+
50+
## Registering Workflows
51+
52+
By default, Workflows are automatically registered simply by declaring a (defworkflow). You may optionally manually declare specific Workflows to register when creating Workers (see [worker-options](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.client.worker#worker-options)).
53+
54+
*It should be noted that the name of the workflow is part of a contract, along with the arguments that the workflow accepts. Therefore, the Workflow definition must be treated with care whenever code is refactored.*
55+
56+
## Starting Workflow Executions
57+
58+
In this Clojure SDK, Workflows are always started with the following flow:
59+
60+
1. Invoke [create-workflow](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.client.core#create-workflow)
61+
2. Invoke [start](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.client.core#start) or [signal-with-start](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.client.core#signal-with-start). The `params` passed to these functions will be forwarded to the workflow and available as `args` in the request map of the Workflow.
62+
3. Gather the asynchronous results with [get-result](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.client.core#get-result) which returns a promise and needs to be dereferenced.
63+
64+
### Example
65+
66+
```clojure
67+
(defworkflow my-workflow
68+
[ctx {{:keys [foo]} :args}]
69+
...)
70+
71+
(let [w (create-workflow client my-workflow {:task-queue "MyTaskQueue"})]
72+
(start w {:foo "bar"})
73+
@(get-result w))
74+
```
75+
76+
## Safe blocking within Workflows
77+
78+
The Temporal Workflow instance behaves like a [Lightweight Process](https://en.wikipedia.org/wiki/Light-weight_process) or [Fiber](https://en.wikipedia.org/wiki/Fiber_(computer_science)). This means the system can generally support a high ratio of Workflow instances to CPUs often in the range of 1000:1 or greater. Achieving this feat requires controlling the IO in and out of the instance in a way that maximizes resource sharing. Therefore, any LWP/Fiber implementation will generally provide its own IO constructions (e.g. mailboxes, channels, promises, etc.), and Temporal is no exception.
79+
80+
In this Clojure SDK, this support comes in a few different flavors:
81+
82+
### Promises
83+
84+
Certain methods naturally return Workflow-safe Promises, such as invoking an Activity from a Workflow. These Workflow-safe Promises have been integrated with the [promesa](https://github.com/funcool/promesa) library. This section serves to document their use and limitations.
85+
86+
#### Safe to use
87+
88+
- Chaining primitives such as [then](https://funcool.github.io/promesa/latest/promesa.core.html#var-then.27), [map](https://funcool.github.io/promesa/latest/promesa.core.html#var-map), and [catch](https://funcool.github.io/promesa/latest/promesa.core.html#var-catch)
89+
90+
#### Unsafe to use
91+
92+
- "Originating" primitives, such as [create](https://funcool.github.io/promesa/latest/promesa.core.html#var-create), [resolved](https://funcool.github.io/promesa/latest/promesa.core.html#var-resolved), and [let](https://funcool.github.io/promesa/latest/promesa.core.html#var-let)
93+
- Aggregating primitives, such as [all](https://funcool.github.io/promesa/latest/promesa.core.html#var-all) and [race](https://funcool.github.io/promesa/latest/promesa.core.html#var-race)
94+
95+
Instead, you must ensure that all promises originate with an SDK provided function, such as [invoke](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.activity#invoke) or [rejected](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.promise#rejected). For aggregating operations, see Temporal Safe options for [all](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.promise#all) and [race](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.promise#race).
96+
97+
What this means in practice is that any promise chain should generally start with some Temporal-native promise.
98+
99+
##### Bad Example
100+
101+
Do NOT do this:
102+
103+
```clojure
104+
(require `[promesa.core :as p])
105+
106+
(-> (p/resolved true)
107+
(p/then (fn [x] (comment "do something with x"))))
108+
```
109+
110+
Placing (p/resolved) (or anything else that ultimately creates a promesa promise) will not work, and you will receive a run-time error.
111+
112+
##### Good example
113+
114+
The proper method is to ensure that a Temporal native operation starts the chain
115+
116+
```clojure
117+
...
118+
(require `[temporal.activity :as a])
119+
120+
(-> (a/invoke some-activity {:some "args"})
121+
(p/then (fn [x] (comment "do something with x"))))
122+
```
123+
124+
##### Watch out for implicit conversion
125+
126+
The following situation can lead to a failure
127+
128+
```clojure
129+
(-> (when some-condition
130+
(a/invoke some-activity {:some "args"}))
131+
(p/then (fn [x] ...)))
132+
```
133+
134+
for situations where some-condition is `false` because promesa will cast the scalar `nil` to (p/resolved nil), thus violating the origination rule. Instead, do this:
135+
136+
```clojure
137+
...
138+
(require `[temporal.promise :as tp])
139+
140+
(-> (if some-condition
141+
(a/invoke some-activity {:some "args"})
142+
(tp/resolved false))
143+
(p/then (fn [x] ...)))
144+
```
145+
146+
Thus ensuring that the origination rules are met regardless of the outcome of the conditional.
147+
148+
### Await
149+
150+
You may use [await](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.core#await) to efficiently parks the Workflow until a provided predicate evaluates to true. The predicate is evaluated at each major state transition of the Workflow.
151+
152+
### Temporal Signals
153+
154+
Your Workflow may send or receive [signals](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.signals).
155+
156+
#### Receiving Signals
157+
158+
Your Workflow may either block waiting with signals with [<!](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.signals#%3C!) or use the non-blocking [poll](https://cljdoc.org/d/io.github.manetu/temporal-sdk/0.7.0/api/temporal.signals#poll). In either case, your Workflow needs to obtain the `signals` context provided in the Worklow request map.
159+
160+
##### Example
161+
162+
```clojure
163+
(defworkflow my-workflow
164+
[ctx {:keys [signals]}]
165+
(let [message (<! signals "MySignal")]
166+
...))
167+
```

0 commit comments

Comments
 (0)