|
12 | 12 | [core.async.flow.example.stats :as stats] |
13 | 13 | [core.async.flow.visualization :as fv])) |
14 | 14 |
|
15 | | -;; # Visualizing core.async.flows |
| 15 | +;; One of Clojure’s superpowers is the ability to coordinate asynchronous operations |
| 16 | +;; using `core.async`. |
| 17 | +;; While powerful, these operations can become hard to reason about as they grow in complexity. |
16 | 18 |
|
17 | | -;; Clojure's async flows are Directed Acyclic Graphs (DAGs) of channel operations. |
18 | | -;; The new [flow-monitor](https://github.com/clojure/core.async.flow-monitor) |
19 | | -;; can visualize these flows. |
| 19 | +;; The new `core.async.flow` library offers a higher-level abstraction for modeling |
| 20 | +;; async processes as a **Directed Acyclic Graph (DAG)**. |
| 21 | +;; And now, with [flow-monitor](https://github.com/clojure/core.async.flow-monitor), |
| 22 | +;; we can *visualize* and *analyze* those flows. |
| 23 | +;; |
| 24 | +;; Let's walk through an exploration of such a flow. |
20 | 25 |
|
21 | 26 | ;; ## What We'll Explore |
22 | 27 |
|
23 | | -;; 1. Basic flow structure (processes, channels, connections) |
24 | | -;; 2. Static visualization of a sample flow |
25 | | -;; 3. Evolution as the flow changes |
| 28 | +;; In this notebook, we'll take a look at: |
| 29 | + |
| 30 | +;; 1. **Basic flow structure**: What does a flow look like under the hood? |
| 31 | +;; 2. **Static visualization**: How can we inspect its components? |
| 32 | +;; 3. **Dynamic interaction**: How do values move through the flow, and what happens when they do? |
| 33 | + |
| 34 | +;; ## 1. Creating a Flow |
| 35 | + |
| 36 | +;; Flows are created from configuration |
26 | 37 |
|
27 | 38 | (def stats-flow |
28 | 39 | (flow/create-flow stats/config)) |
29 | 40 |
|
| 41 | +;; This flow models a small system involving aggregation, notification, and reporting. |
| 42 | +;; Internally, it consists of processes connected via channels. |
| 43 | + |
| 44 | +;; ## 2. Inspecting the Flow |
| 45 | + |
| 46 | +;; Flows implement the `Datafy` protocol so we can inspect them as data. |
| 47 | + |
| 48 | +(datafy/datafy stats-flow) |
| 49 | + |
| 50 | +;; That's a lot to take in! Fortunately, we can make things more digestible |
| 51 | +;; by viewing just the **processes** involved. |
| 52 | + |
30 | 53 | (fv/proc-table stats-flow) |
31 | 54 |
|
| 55 | +;; This table gives us a clear list of components in the flow, including their names |
| 56 | +;; and behaviors. |
| 57 | + |
| 58 | +;; Next, let’s examine how these processes are **connected**. |
| 59 | + |
32 | 60 | (fv/conn-table stats-flow) |
33 | 61 |
|
| 62 | +;; Now we’re seeing the wiring: who talks to whom, and through what channels. |
| 63 | + |
| 64 | +;; ## 3. Running the Flow |
| 65 | + |
| 66 | +;; Time to bring our flow to life! |
| 67 | +;; Calling `start` activates the processes and returns a map of the important channels for interaction. |
| 68 | + |
34 | 69 | (def chs (flow/start stats-flow)) |
35 | 70 |
|
| 71 | +;; We can now **inject values** into specific points in the flow. |
| 72 | +;; Think of this like poking the system and watching how it reacts. |
| 73 | + |
| 74 | +;; We send a “poke” signal to the `aggregator` process. |
| 75 | + |
36 | 76 | @(flow/inject stats-flow [:aggregator :poke] [true]) |
37 | | -@(flow/inject stats-flow [:aggregator :stat] ["abc1000"]) ;; trigger an alert |
| 77 | + |
| 78 | +;; We send a stat string that is designed to trigger an alert. |
| 79 | + |
| 80 | +@(flow/inject stats-flow [:aggregator :stat] ["abc1000"]) |
| 81 | + |
| 82 | +;; We send a notification message into the `notifier`. |
| 83 | + |
38 | 84 | @(flow/inject stats-flow [:notifier :in] [:sandwich]) |
39 | 85 |
|
| 86 | +;; ## 4. Observing the Results |
| 87 | + |
| 88 | +;; Our flow includes a `report-chan`, where summaries and reports might be sent. |
| 89 | + |
40 | 90 | (def report-chan (:report-chan chs)) |
| 91 | + |
41 | 92 | (flow/ping stats-flow) |
| 93 | + |
42 | 94 | (async/poll! report-chan) |
| 95 | + |
| 96 | +;; After pinging the system, we check if anything landed in the report channel. |
| 97 | + |
| 98 | +;; We can also inspect the `error-chan`, where any issues in the flow are reported. |
| 99 | + |
43 | 100 | (def error-chan (:error-chan chs)) |
| 101 | + |
44 | 102 | (async/poll! error-chan) |
45 | 103 |
|
| 104 | +;; If something unexpected occurred (e.g., bad input or failed routing), |
| 105 | +;; this is where we’d find it. |
| 106 | +;; |
| 107 | +;; |
| 108 | +;; |
46 | 109 | ;;(flow/stop stats-flow) |
47 | 110 | ;;(async/close! stat-chan) |
48 | 111 |
|
49 | 112 | ;; @(flow/inject stats-flow [:aggregator :poke] [true]) |
50 | 113 |
|
51 | | -(datafy/datafy stats-flow) |
| 114 | + |
| 115 | +; ## Summary |
| 116 | + |
| 117 | +;; By constructing, inspecting, and interacting with a flow, we can understand the |
| 118 | +;; lifecycle and structure of asynchronous systems more clearly. |
| 119 | +;; |
| 120 | +;; This toolset provides a bridge between the abstract beauty of DAGs and the |
| 121 | +;; gritty realism of channel communication—unlocking both power and clarity |
| 122 | +;; in asynchronous Clojure code. |
| 123 | + |
| 124 | +;; Happy flowing! |
0 commit comments