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::*;