app_frame/
lib.rs

1//! [github](https://github.com/dnut/app-frame) ·
2//! [crates.io](https://crates.io/crates/app-frame) ·
3//! [docs.rs](https://docs.rs/app-frame)
4//!
5//! App Frame is a compile-time dependency-injected application framework with a
6//! service orchestrator. It has two general goals:
7//! 1. Reliably run and monitor multiple long-running services, recovering from
8//!    any errors.
9//! 2. Reduce the boilerplate and maintenance cost of manually constructing
10//!    application components that have a complex dependency graph. You should
11//!    only need to describe your application's components. The rust compiler
12//!    can wire them up.
13//!
14//! At compile-time, the framework guarantees that all necessary dependencies
15//! will be injected upon application startup. At runtime, the framework runs
16//! your custom initialization code, then starts your services, automatically
17//! injects their dependencies, monitors their health, restarts unhealthy
18//! services, and reports their status to external http health checks.
19//!
20//! Application frameworks add complexity and obscure control flow, but they can
21//! also save a lot of time with setup and maintenance tasks. To be sure
22//! app-frame is right for your project, see the [Trade-offs](#trade-offs)
23//! section.
24//!
25//! ## Usage
26//!
27//! ```toml
28//! [dependencies]
29//! app-frame = "0.3.1"
30//! ```
31//!
32//! App Frame provides macros for convenience. If they seem too esoteric or
33//! inflexible, you can instead implement traits to wire up your application.
34//!
35//! This trivial example illustrates the bare minimum boilerplate to use the
36//! framework with its macros, but doesn't actually run anything useful.
37//!
38//! ```rust
39//! use app_frame::{application, service_manager::Application, Never};
40//!
41//! #[tokio::main]
42//! async fn main() -> anyhow::Result<Never> {
43//!     MyApp.run().await
44//! }
45//!
46//! pub struct MyApp;
47//!
48//! application!(self: MyApp);
49//! ```
50//!
51//! Here is the equivalent application, with direct trait implementation instead
52//! of macros:
53//!
54//! ```rust
55//! use app_frame::{application, service_manager::Application, Never};
56//!
57//! #[tokio::main]
58//! async fn main() -> anyhow::Result<Never> {
59//!     MyApp.run().await
60//! }
61//!
62//! pub struct MyApp;
63//!
64//! impl Initialize for MyApp {}
65//!
66//! impl Serves for MyApp {
67//!     fn services(&self) -> Vec<Box<dyn Service>> {
68//!         vec![]
69//!     }
70//! }
71//! ```
72//!
73//! To take full advantage of app-frame, you should define dependency
74//! relationships and explicitly declare every component that you want to run as
75//! part of your application.
76//!
77//! ### Make Components Injectible
78//!
79//! Make a struct injectible if you want its dependencies to be injected by
80//! app-frame, with either a macro or trait:
81//! - **macro:** Wrap the struct with the `inject!` macro. In a future release,
82//!   this will be an attribute-style macro.
83//! - **traits:** `impl<T> From<&T> for C where T: Provides<D>` for the
84//!   component `C` and each of its dependencies `D`.
85//! ```rust
86//! /// Macro approach. This automatically implements:
87//! /// impl<T> From<&T> for InitJob
88//! /// where
89//! ///       T: Provides<Arc<dyn Repository>> + Provides<Component2> {...}
90//! inject!(
91//!     pub struct Component1 {
92//!         repo: Arc<dyn Repository>,
93//!         component2: Component2,
94//!     }
95//! );
96//! ```
97//!
98//! ```rust
99//! /// Trait approach
100//! ///
101//! /// This is practical here since only one item needs to be injected,
102//! /// and the others can be set to default values. The inject macro
103//! /// does not yet support defaults.
104//! impl<T> From<&T> for MyService
105//! where
106//!     T: Provides<Arc<dyn Repository>>,
107//! {
108//!     fn from(p: &T) -> Self {
109//!         Self {
110//!             repository: p.provide(),
111//!             heartbeat: Arc::new(()),
112//!             my_health_metric: true,
113//!         }
114//!     }
115//! }
116//! ```
117//!
118//! ### Define Services
119//! App Frame is built with the assumption that it will trigger some long
120//! running services on startup. To define a long-running service, either
121//! implement `Service`, or implement `Job` and `SelfConfiguredLoop`.
122//! ```rust
123//! /// Implement Service when you already have some logic that runs forever
124//! #[async_trait]
125//! impl Service for MyService {
126//!     async fn run_forever(&self) -> Never {
127//!         // This is a trivial example. You can call something that you expect to run forever.
128//!         loop {
129//!             self.heartbeat.beat();
130//!         }
131//!     }
132//!
133//!     fn heartbeat_ttl(&self) -> i32 {
134//!         60
135//!     }
136//!
137//!     fn set_heartbeat(&mut self, heartbeat: Arc<dyn Heartbeat + 'static>) {
138//!         self.heartbeat = heartbeat;
139//!     }
140//!
141//!     fn is_healthy(&self) -> bool {
142//!         self.my_health_metric
143//!     }
144//! }
145//! ```
146//!
147//! ```rust
148//! /// Implementing these traits will let you use a job that is expected to terminate
149//! /// after a short time. It becomes a service that runs the job repeatedly.
150//! #[async_trait]
151//! impl Job for JobToLoopForever {
152//!     async fn run_once(&self) -> anyhow::Result<()> {
153//!         self.solve_halting_problem()
154//!     }
155//! }
156//!
157//! impl SelfConfiguredLoop for JobToLoopForever {
158//!     fn loop_config(&self) -> LoopConfig {
159//!         LoopConfig {
160//!             delay_secs: 10,
161//!             max_iteration_secs: 20,
162//!         }
163//!     }
164//! }
165//! ```
166//!
167//!
168//! ### Declare the app
169//! Define a struct representing your app and populate it with any configuration
170//! or singletons.
171//!
172//! Any components that are automatically constructed by the framework will be
173//! instantiated once for every component that depends on it. If you want a
174//! singleton that is created once and reused, it needs to be instantiated
175//! manually, like this example.
176//! ```rust
177//! pub struct MyApp {
178//!     db_singleton: Arc<DatabaseConnectionPoolSingleton>,
179//! }
180//!
181//! impl MyApp {
182//!     pub fn new(database_config: &str) -> Self {
183//!         Self {
184//!             db_singleton: Arc::new(DatabaseConnectionPoolSingleton {
185//!                 conn: database_config.into(),
186//!             }),
187//!         }
188//!     }
189//! }
190//! ```
191//!
192//! ### Declare application components.
193//! Many dependency injection frameworks only need you to define your
194//! components, and the framework will locate them and create them as needed.
195//! App Frame instead prefers explicitness. You must list all application
196//! components in a single place, but App Frame will figure out how to wire them
197//! together. This is intended to make the framework's control flow easier to
198//! follow, though this benefit may be offset by using macros.
199//!
200//! - **macro:** List all of your app's components in the `application!` macro
201//!   as documented below.
202//! - **traits:**
203//!   - `impl Initialize for MyApp`, providing any `Job`s that need to run at
204//!     startup.
205//!   - `impl Serves for MyApp`, providing any `Service`s that need to run
206//!     continuously.
207//!   - `impl Provides<D> for MyApp` for each dependency `D` that will be needed
208//!     either directly or transitively by any job or service provided above.
209//!
210//!
211//! ### Customize Service Orchestration
212//!
213//! You can customize monitoring and recovery behavior by starting your app with
214//! the `run_custom` method.
215//!
216//! ```rust
217//! // This is the default config, which is used when you call `MyApp::new().run()`
218//! // See rustdocs for RunConfig and HealthEndpointConfig for more details.
219//! MyApp::new()
220//!     .run_custom(RunConfig {
221//!         log_interval: 21600,
222//!         attempt_recovery_after: 120,
223//!         http_health_endpoint: Some(HealthEndpointConfig {
224//!             port: 3417,
225//!             success_status: StatusCode::OK,
226//!             fail_status: StatusCode::INTERNAL_SERVER_ERROR,
227//!         }),
228//!     })
229//!     .await
230//! ```
231//!
232//! ### Full macro-based example
233//!
234//! This example defines and injects various types of components to illustrate
235//! the various features provided by the framework. This code actually runs an
236//! application, and will respond to health checks indicating that 2 services
237//! are healthy.
238//!
239//! ```rust
240//! use std::sync::Arc;
241//!
242//! use async_trait::async_trait;
243//!
244//! use app_frame::{
245//!     application,
246//!     dependency_injection::Provides,
247//!     inject,
248//!     service::{Job, LoopConfig, LoopingJobService, SelfConfiguredLoop, Service},
249//!     service_manager::{Application, Heartbeat},
250//!     Never,
251//! };
252//!
253//! #[tokio::main]
254//! async fn main() -> anyhow::Result<Never> {
255//!     MyApp::new("db://host").run().await
256//! }
257//!
258//! pub struct MyApp {
259//!     db_singleton: Arc<DatabaseConnectionPoolSingleton>,
260//! }
261//!
262//! impl MyApp {
263//!     pub fn new(database_config: &str) -> Self {
264//!         Self {
265//!             db_singleton: Arc::new(DatabaseConnectionPoolSingleton {
266//!                 conn: database_config.into(),
267//!             }),
268//!         }
269//!     }
270//! }
271//!
272//! // Including a type here implements Provides<ThatType> for MyApp.
273//! //
274//! // Struct definitions wrapped in the `inject!` macro get a From<T>
275//! // implementation where T: Provides<U> for each field of type U in the struct.
276//! // When those structs are provided as a component here, they will be constructed
277//! // with the assumption that MyApp impl Provides<U> for each of those U's
278//! //
279//! // All the types provided here are instantiated separately each time they are
280//! // needed. If you want to support a singleton pattern, you need to construct the
281//! // singletons in the constructor for this type and wrap them in an Arc. Then you
282//! // can provide them in the "provided" section by cloning the Arc.
283//! application! {
284//!     self: MyApp
285//!
286//!     // init jobs are types implementing `Job` with a `run_once` function that
287//!     // needs to run once during startup.
288//!     // - constructed the same way as a component
289//!     // - made available as a dependency, like a component
290//!     // - wrap in curly braces for custom construction of an iterable of jobs.
291//!     init [
292//!         InitJob
293//!     ]
294//!
295//!     // Services are types with a `run_forever` function that needs to run for
296//!     // the entire lifetime of the application.
297//!     // - constructed the same way as a component
298//!     // - made available as a dependency, like a component
299//!     // - registered as a service and spawned on startup.
300//!     // - wrap in curly braces for custom construction of an iterable of
301//!     //   services.
302//!     // - Use 'as WrapperType' if it needs to be wrapped in order to get
303//!     //   something that implements `Service`. wrapping uses WrapperType::from().
304//!     services [
305//!         MyService,
306//!         JobToLoopForever as LoopingJobService,
307//!     ]
308//!
309//!     // Components are items that will be provided as dependencies to anything
310//!     // that needs it. This is similar to the types provided in the "provides"
311//!     // section, except that components can be built exclusively from other
312//!     // components and provided types, whereas "provides" items depend on other
313//!     // state or logic.
314//!     // - constructed via Type::from(MyApp). Use the inject! macro on the
315//!     //   type to make this possible.
316//!     // - Use `as dyn SomeTrait` if you also want to provide the type as the
317//!     //   implementation for Arc<dyn SomeTrait>
318//!     components [
319//!         Component1,
320//!         Component2,
321//!         DatabaseRepository as dyn Repository,
322//!     ]
323//!
324//!     // Use this when you want to provide a value of some type that needs to either:
325//!     // - be constructed by some custom code you want to write here.
326//!     // - depend on some state that was initialized in MyApp.
327//!     //
328//!     // Syntax: Provide a list of the types you want to provide, followed by the
329//!     // expression that can be used to instantiate any of those types.
330//!     // ```
331//!     // TypeToProvide: { let x = self.get_x(); TypeToProvide::new(x) },
332//!     // Arc<dyn Trait>, Arc<ConcreteType>: Arc::new(ConcreteType::default()),
333//!     // ```
334//!     provided {
335//!         Arc<DatabaseConnectionPoolSingleton>: self.db_singleton.clone(),
336//!     }
337//! }
338//!
339//! inject!(
340//!     pub struct InitJob {
341//!         repo: Arc<dyn Repository>,
342//!     }
343//! );
344//!
345//! #[async_trait]
346//! impl Job for InitJob {
347//!     async fn run_once(&self) -> anyhow::Result<()> {
348//!         Ok(())
349//!     }
350//! }
351//!
352//! inject!(
353//!     pub struct JobToLoopForever {
354//!         c1: Component1,
355//!         c2: Component2,
356//!     }
357//! );
358//!
359//! #[async_trait]
360//! impl Job for JobToLoopForever {
361//!     async fn run_once(&self) -> anyhow::Result<()> {
362//!         Ok(())
363//!     }
364//! }
365//!
366//! impl SelfConfiguredLoop for JobToLoopForever {
367//!     fn loop_config(&self) -> LoopConfig {
368//!         LoopConfig {
369//!             delay_secs: 10,
370//!             max_iteration_secs: 20,
371//!         }
372//!     }
373//! }
374//!
375//! inject!(
376//!     pub struct Component1 {}
377//! );
378//!
379//! inject!(
380//!     pub struct Component2 {
381//!         repo: Arc<dyn Repository>,
382//!     }
383//! );
384//!
385//! pub trait Repository: Send + Sync {}
386//!
387//! pub struct MyService {
388//!     repository: Arc<dyn Repository>,
389//!     heartbeat: Arc<dyn Heartbeat + 'static>,
390//!     my_health_metric: bool,
391//! }
392//!
393//! /// This is how you provide a custom alternative to the `inject!` macro, it is
394//! /// practical here since only one item needs to be injected, and the others can
395//! /// be set to default values.
396//! impl<T> From<&T> for MyService
397//! where
398//!     T: Provides<Arc<dyn Repository>>,
399//! {
400//!     fn from(p: &T) -> Self {
401//!         Self {
402//!             repository: p.provide(),
403//!             heartbeat: Arc::new(()),
404//!             my_health_metric: true,
405//!         }
406//!     }
407//! }
408//!
409//! #[async_trait]
410//! impl Service for MyService {
411//!     async fn run_forever(&self) -> Never {
412//!         loop {
413//!             self.heartbeat.beat();
414//!         }
415//!     }
416//!
417//!     fn heartbeat_ttl(&self) -> i32 {
418//!         60
419//!     }
420//!
421//!     fn set_heartbeat(&mut self, heartbeat: Arc<dyn Heartbeat + 'static>) {
422//!         self.heartbeat = heartbeat;
423//!     }
424//!
425//!     fn is_healthy(&self) -> bool {
426//!         self.my_health_metric
427//!     }
428//! }
429//!
430//! inject!(
431//!     pub struct DatabaseRepository {
432//!         connection: Arc<DatabaseConnectionPoolSingleton>,
433//!     }
434//! );
435//!
436//! impl Repository for DatabaseRepository {}
437//!
438//! pub struct DatabaseConnectionPoolSingleton {
439//!     conn: String,
440//! }
441//! ```
442//!
443//! # Trade-offs
444//! Application frameworks are often not worth the complexity, but they do have
445//! utility in many cases. App Frame would typically be useful in a complicated
446//! backend web service with a lot of connections to other services, or whenever
447//! the following conditions are met:
448//!
449//! - Multiple fallible long running tasks need to run indefinitely in parallel
450//!   with monitoring and recovery.
451//! - You want to use tokio's event loop to run suspending functions in
452//!   parallel.
453//! - The decoupling achieved by dependency inversion is beneficial enough to be
454//!   worth the complexity of introducing layers of abstraction.
455//! - The application has a complex dependency graph of internal components, and
456//!   you'd like to make it easier to change the dependency relationships in the
457//!   future.
458//! - You want to be explicit, in a single place, about every component that
459//!   should actually be instantiated when the app starts. This is a key
460//!   differentiator from most other dependency injection frameworks.
461//! - You don't mind gigantic macros that only make sense after reading
462//!   documentation. Macros are not required to use App Frame, but you can use
463//!   them to significantly reduce boilerplate.
464//! - You are willing to compromise some performance and readability with the
465//!   indirection of vtables and smart pointers.
466//!
467
468/// Exponential backoff tracker to inform callers when to take an action.
469pub mod backoff;
470/// Define a type as a dependent or a dependency provider.
471pub mod dependency_injection;
472/// Simple and versatile error handling and logging.
473pub mod error;
474/// Externally facing http endpoint to report service health.
475pub mod health_endpoint;
476/// How to consume and process items from a queue.
477pub mod queue_consumer;
478/// Defines which behaviors are required to define a job or service.
479pub mod service;
480/// Runs services and monitors their health.
481pub mod service_manager;
482/// Clock dependencies that are easily swapped out and mocked, to reduce direct dependencies on syscalls.
483pub mod time;
484
485/// misc unrelated items that are too small to get their own files,
486/// kept out of this file to reduce clutter.
487mod util;
488pub use util::*;