paladin/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! Distributed computation library for Rust.
4//!
5//! Paladin aims to simplify the challenge of writing distributed programs. It
6//! provides a declarative API, allowing developers to articulate their
7//! distributed programs clearly and concisely, without thinking about the
8//! complexities of distributed systems programming.
9//!
10//! Features:
11//! - **Declarative API**: Express distributed computations with clarity and
12//!   ease.
13//! - **Automated Distribution**: Paladin’s runtime seamlessly handles task
14//!   distribution and execution across the cluster.
15//! - **Simplified Development**: Concentrate on the program logic, leaving the
16//!   complexities of distributed systems to Paladin.
17//! - **Infrastructure Agnostic**: Paladin is generic over its messaging backend
18//!   and infrastructure provider. Bring your own infra!
19//!
20//! # How to use Paladin
21//!
22//! When writing your programs, you will interact with two core APIs,
23//! [`Operation`](crate::operation::Operation)s and
24//! [`Directive`](crate::directive::Directive)s. You will define your
25//! distributed computations in terms of
26//! [`Operation`](crate::operation::Operation)s, and construct your programs
27//! using Paladin's provided [`Directive`](crate::directive::Directive)s.
28//!
29//! In general, Paladin assumes that distributed programs will fundamentally
30//! operate over a stream or async-iterator-like data structure. In particular,
31//! Paladin's [`Directive`](crate::directive::Directive) API operates over
32//! [functorial](crate::directive::Functor) or
33//! [foldable](crate::directive::Foldable) data structures, providing methods
34//! [`map`](crate::directive::Directive::map) and
35//! [`fold`](crate::directive::Directive::fold), respectively. This
36//! generalization allows Paladin to support a wide variety of parallel data
37//! structures. Paladin provides one such data structure out of the box,
38//! [`IndexedStream`](crate::directive::indexed_stream::IndexedStream).
39//! [`IndexedStream`](crate::directive::indexed_stream::IndexedStream)
40//! implements both the [`Functor`](crate::directive::Functor) and
41//! [`Foldable`](crate::directive::Foldable) traits, and as such should be able
42//! to handle any distributed algorithm that can be expressed in terms of
43//! [`map`](crate::directive::Directive::map) and
44//! [`fold`](crate::directive::Directive::fold). We recommend using
45//! [`IndexedStream`](crate::directive::indexed_stream::IndexedStream) for all
46//! your programs, as it has been highly optimized for parallelism, although
47//! of course you are free to define your own.
48//!
49//! ## Defining Operations
50//!
51//! Operations are the semantic building blocks of your system. By implementing
52//! [`Operation`](crate::operation::Operation) for a type, it can be given to
53//! [`Directive`](crate::directive::Directive)s and remotely executed.
54//!
55//! ```
56//! use paladin::{RemoteExecute, operation::{Operation, Result}};
57//! use serde::{Deserialize, Serialize};
58//!
59//! #[derive(Serialize, Deserialize, RemoteExecute)]
60//! struct FibAt;
61//!
62//! impl Operation for FibAt {
63//!     type Input = u64;
64//!     type Output = u64;
65//!
66//!     fn execute(&self, input: Self::Input) -> Result<Self::Output> {
67//!         match input {
68//!             0 => Ok(0),
69//!             1 => Ok(1),
70//!             _ => {
71//!                 let mut a = 0;
72//!                 let mut b = 1;
73//!                 for _ in 2..=input {
74//!                     let temp = a;
75//!                     a = b;
76//!                     b = temp + b;
77//!                 }
78//!                 Ok(b)
79//!             }
80//!         }
81//!     }
82//! }
83//!
84//! # fn main() {
85//! assert_eq!(FibAt.execute(10).unwrap(), 55);
86//! # }
87//! ```
88//!
89//! ## Constructing a program
90//!
91//! Once operations have been defined, they can be plugged into Paladin's
92//! [`Directive`](crate::directive::Directive)s to construct a distributed
93//! program.
94//!
95//! ```
96//! use paladin::{RemoteExecute, operation::{Operation, Result}};
97//! use serde::{Deserialize, Serialize};
98//! #
99//! # #[derive(Serialize, Deserialize, RemoteExecute)]
100//! # struct FibAt;
101//! #
102//! # impl Operation for FibAt {
103//! #    type Input = u64;
104//! #    type Output = u64;
105//! #
106//! #    fn execute(&self, input: Self::Input) -> Result<Self::Output> {
107//! #        match input {
108//! #            0 => Ok(0),
109//! #            1 => Ok(1),
110//! #            _ => {
111//! #                let mut a = 0;
112//! #                let mut b = 1;
113//! #                for _ in 2..=input {
114//! #                    let temp = a;
115//! #                    a = b;
116//! #                    b = temp + b;
117//! #                }
118//! #                Ok(b)
119//! #            }
120//! #        }
121//! #    }
122//! # }
123//! #
124//! use paladin::{
125//!     operation::Monoid,
126//!     directive::{indexed_stream::IndexedStream, Directive},
127//!     runtime::Runtime,
128//! };
129//!
130//! // Define a Sum monoid.
131//! #[derive(Serialize, Deserialize, RemoteExecute)]
132//! struct Sum;
133//!
134//! impl Monoid for Sum {
135//!     type Elem = u64;
136//!
137//!     fn combine(&self, a: Self::Elem, b: Self::Elem) -> Result<Self::Elem> {
138//!        Ok(a + b)
139//!     }
140//!
141//!     fn empty(&self) -> Self::Elem {
142//!        0
143//!     }
144//! }
145//!
146//! #[tokio::main]
147//! async fn main() -> anyhow::Result<()> {
148//!     let runtime = Runtime::in_memory().await?;
149//!     let stream = IndexedStream::from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
150//!     // Compute the fibonacci number at each element in the stream with our
151//!     // previously declared `FibAt` operation.
152//!     let fibs = stream.map(&FibAt);
153//!     // Sum the fibonacci numbers.
154//!     let sum = fibs.fold(&Sum);
155//!
156//!     // Run the computation.
157//!     let result = sum.run(&runtime).await;
158//!
159//!     // Close the runtime
160//!     runtime.close().await?;
161//!
162//!     assert_eq!(result?, 143);
163//! #   Ok(())
164//! }
165//! ```
166//!
167//! In this example program, we define an algorithm for computing the fibonacci
168//! number at each element in a stream, and then summing the results. Behind the
169//! scenes, Paladin will distribute the computations across the cluster and
170//! return the result back to the main thread. Note that in this example, we're
171//! using Paladin's multi-threaded in-memory runtime, which can be useful for
172//! testing and debugging. In a real-world setting, one would use a distributed
173//! runtime, such as Paladin's AMQP runtime.
174//!
175//! ## Application and deployment architecture
176//!
177//! We suggest the following project layout:
178//! ```bash
179//! ops
180//! ├── Cargo.toml
181//! └── src
182//!    └── lib.rs
183//! worker
184//! ├── Cargo.toml
185//! └── src
186//!    └── main.rs
187//! leader
188//! ├── Cargo.toml
189//! └── src
190//!    └── main.rs
191//! ```
192//!
193//! Here's a breakdown:
194//! - `ops`: A library with your operation definitions and their registry,
195//!   shared between `worker` and `leader`.
196//! - `worker`: Executes operations on remote machines.
197//! - `leader`: Coordinates the distributed computation.
198//!
199//! For deployment, use one `leader` for multiple `workers`. Currently, only one
200//! `leader` deployment is supported. Future versions might offer state
201//! persistence for better failover and fault tolerance.
202//!
203//! ### Worker main
204//!
205//! Given that virtually all workers will do exactly the same thing, Paladin's
206//! worker runtime provides a
207//! [`WorkerRuntime::main_loop`](crate::runtime::WorkerRuntime::main_loop). In
208//! general, most of your logic should exist in `ops` and `leader`.
209pub mod acker;
210pub mod channel;
211pub mod common;
212pub mod config;
213pub mod contiguous;
214pub mod directive;
215pub mod operation;
216pub mod queue;
217pub mod runtime;
218pub mod serializer;
219pub mod task;
220pub use async_trait::async_trait;
221pub use paladin_opkind_derive::*;
222
223// Not public API. Used by generated code.
224#[doc(hidden)]
225pub mod __private {
226    #[doc(hidden)]
227    pub use bytes;
228    #[doc(hidden)]
229    pub use futures;
230    #[doc(hidden)]
231    pub use linkme;
232    #[doc(hidden)]
233    pub use tokio;
234    #[doc(hidden)]
235    pub use tracing;
236
237    #[doc(hidden)]
238    #[linkme::distributed_slice]
239    pub static OPERATIONS: [fn(
240        crate::task::AnyTask,
241    ) -> futures::future::BoxFuture<
242        'static,
243        crate::operation::Result<crate::task::AnyTaskOutput>,
244    >];
245}