1- # barnacle
1+ # Barnacle
22
33[ ![ Package Version] ( https://img.shields.io/hexpm/v/barnacle )] ( https://hex.pm/packages/barnacle )
44[ ![ Hex Docs] ( https://img.shields.io/badge/hex-docs-ffaff3 )] ( https://hexdocs.pm/barnacle/ )
55
6+ Self-healing clusters for Gleam applications on the BEAM!
7+
8+ Connect to other BEAM nodes, and automatically reconnect when they go down.
9+
10+ ## Features
11+
12+ - Discover nodes using different strategies. Built-in strategies:
13+ - Local EPMD
14+ - Remote EPMD
15+ - DNS
16+ - Automatically reconnect to nodes when they come back online
17+ - Supply your own strategies
18+ - Listen to events
19+ - Trigger refreshes manually
20+
21+ ## Getting Started
22+
23+ ``` sh
24+ gleam add barnacle
25+ ```
26+
27+ You'll also need to start your Gleam process with a node name, and set the cookie.
28+ You can do this using the ` ERL_FLAGS ` environment variable.
29+
30+ > [ !NOTE]
31+ > There's a Fly.io example coming soon.
32+
633``` sh
7- gleam add barnacle@1
34+ ERL_FLAGS=" -name my_app@127.0.0.1 -setcookie my_cookie" gleam run
35+ ```
36+
37+ ### Start Barnacle under a Supervisor (recommended)
38+
39+ ``` gleam
40+ import barnacle
41+ import gleam/erlang/process
42+ import gleam/otp/supervisor
43+
44+ pub fn main() {
45+ // Configure your Barnacle
46+ let barnacle =
47+ barnacle.local_epmd()
48+ |> barnacle.with_poll_interval(15_000)
49+ |> barnacle.with_name("my_barnacle")
50+
51+ // Create a process to receive the child process later
52+ let self = process.new_subject()
53+
54+ // Start the child process under a supervisor
55+ let barnacle_worker = barnacle.child_spec(barnacle, None)
56+ let assert Ok(_) = supervisor.start(supervisor.add(_, barnacle_worker))
57+
58+ // Get a subject to send messages to the child process
59+ let assert Ok(barnacle_subject) = process.receive(self, 10_000)
60+
61+ // Continue your setup...
62+
63+ process.sleep_forever()
64+ }
65+ ```
66+
67+ ### Start Barnacle as a standalone actor
68+
69+ This is ** not** recommended as your barnacle won't be restarted if it crashes for any
70+ reason.
71+
72+ ``` gleam
73+ import barnacle
74+ import gleam/erlang/process
75+
76+ pub fn main() {
77+ // Configure your Barnacle
78+ let barnacle =
79+ barnacle.local_epmd()
80+ |> barnacle.with_poll_interval(15_000)
81+ |> barnacle.with_name("my_barnacle")
82+
83+ // Start the actor
84+ let barnacle_subject = barnacle.start(barnacle)
85+
86+ // Continue your setup...
87+
88+ process.sleep_forever()
89+ }
90+ ```
91+
92+ ## Strategies
93+
94+ Barnacle ships with a few built-in strategies, but you can also supply your own by
95+ providing a single callback function.
96+
97+ ### Local EPMD
98+
99+ Discover nodes using the local EPMD. This will automatically attempt to connect to
100+ nodes that are running on the same machine.
101+
102+ ``` gleam
103+ import barnacle
104+
105+ pub fn main() {
106+ barnacle.local_epmd()
107+ |> barnacle.start
108+ }
109+ ```
110+
111+ ### Remote EPMD
112+
113+ Connect to nodes using a known list.
114+
115+ ``` gleam
116+ import barnacle
117+ import gleam/erlang/atom
118+
119+ pub fn main() {
120+ barnacle.epmd(
121+ ["node1@192.168.1.1", "node2@192.168.1.2"]
122+ |> list.map(atom.create_from_string),
123+ )
124+ |> barnacle.start
125+ }
126+ ```
127+
128+ ### DNS
129+
130+ Discover nodes using DNS.
131+
132+ The first argument is the basename of the node. Currently, Barnacle only supports
133+ connecting to nodes with the same basename.
134+
135+ Barnacle provides a helper function to get the basename of the current node.
136+
137+ ``` gleam
138+ import barnacle
139+
140+ pub fn main() {
141+ let assert Ok(basename) = barnacle.get_node_basename(node.self())
142+ barnacle.dns(
143+ basename,
144+ // The hostname to query against
145+ "my_app.example.com",
146+ )
147+ |> barnacle.start
148+ }
149+ ```
150+
151+ ### Creating a custom strategy
152+
153+ You can create your own strategy using the ` new_strategy ` function.
154+
155+ ``` gleam
156+ import barnacle
157+
158+ pub fn main() {
159+ barnacle.new_strategy(
160+ // Discover nodes
161+ fn() {
162+ Ok([])
163+ },
164+ )
165+ |> barnacle.custom
166+ |> barnacle.start
167+ }
168+ ```
169+
170+ You can also supply your own functions for connecting, disconnecting, and listing nodes.
171+ If these are not supplied, the built-in functions will be used.
172+
173+ This may be useful if you want to use an alternative to Distributed Erlang, such as
174+ [ Partisan] ( https://github.com/lasp-lang/partisan ) .
175+
176+ ``` gleam
177+ import barnacle
178+
179+ pub fn main() {
180+ barnacle.new_strategy(my_discover_function)
181+ |> barnacle.with_connect_nodes_function(my_connect_function)
182+ |> barnacle.with_disconnect_nodes_function(my_disconnect_function)
183+ |> barnacle.with_list_nodes_function(my_list_function)
184+ |> barnacle.custom
185+ |> barnacle.start
186+ }
8187```
188+
189+ You can find more information about creating custom strategies in the
190+ [ docs] ( /barnacle/barnacle.html#custom ) .
191+
192+ ## Events
193+
194+ You can provide a custom subject to receive events from your barnacle.
195+ This will be notified whenever the barnacle refreshes, is paused, or is shutdown.
196+
9197``` gleam
10198import barnacle
199+ import gleam/erlang/process
11200
12201pub fn main() {
13- // TODO: An example of the project in use
202+ let self = process.new_subject()
203+
204+ // Configure your Barnacle
205+ let barnacle =
206+ barnacle.local_epmd()
207+ |> barnacle.with_poll_interval(15_000)
208+ |> barnacle.with_name("my_barnacle")
209+ |> barnacle.with_listener(self)
210+
211+ // Start the actor
212+ barnacle.start(barnacle)
213+
214+ // Wait for the barnacle to refresh
215+ process.sleep(10_000)
216+ let assert Ok(barnacle.RefreshResponse(Ok(new_nodes))) = process.receive(self, 10_000)
217+ }
218+ ```
219+
220+ ## Manual interactions
221+
222+ You can interact with your barnacle manually by sending messages to it.
223+
224+ ``` gleam
225+ import barnacle
226+ import gleam/erlang/process
227+ import gleam/otp/actor
228+
229+ pub fn main() {
230+ // Start a barnacle
231+ let barnacle_subject =
232+ barnacle.local_epmd()
233+ |> barnacle.start
234+
235+ let assert Ok(_) = barnacle.pause(barnacle_subject, 1000)
236+ let assert Ok(_) = barnacle.refresh(barnacle_subject, 10_000)
237+ let assert Ok(_) = barnacle.shutdown(barnacle_subject, 1000)
14238}
15239```
16240
@@ -22,3 +246,24 @@ Further documentation can be found at <https://hexdocs.pm/barnacle>.
22246gleam run # Run the project
23247gleam test # Run the tests
24248```
249+
250+ If you would like to contribute, please open an issue or a PR. New strategies are
251+ welcome, though try to keep dependencies to a minimum.
252+
253+ ## TODO
254+
255+ - [ ] Add new strategies
256+ - [ ] Kubernetes
257+ - [ ] Kubernetes with DNS
258+ - [ ] Multicast UDP gossip
259+ - [ ] ` .hosts.erlang ` file
260+ - [ ] Add a Fly.io example
261+
262+ ## With thanks
263+
264+ A lot of inspiration came from the following projects:
265+
266+ - [ libcluster] ( https://github.com/bitwalker/libcluster )
267+ - [ nessie_cluster] ( https://github.com/ckreiling/nessie_cluster )
268+
269+ Thanks!
0 commit comments