app-frame 0.3.0

Compile-time dependency-injected application framework with a service orchestrator.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
# App Frame

[github](https://github.com/dnut/app-frame)
· [crates.io](https://crates.io/crates/app-frame)
· [docs.rs](https://docs.rs/app-frame)

App Frame is a compile-time dependency-injected application framework with a service orchestrator. It has two general goals:
1. Reliably run and monitor multiple long-running services, recovering from any errors.
2. Reduce the boilerplate and maintenance cost of manually constructing application components that have a complex dependency graph. You should only need to describe your application's components. The rust compiler can wire them up.

At compile-time, the framework guarantees that all necessary dependencies will be injected upon application startup. At runtime, the framework runs your custom initialization code, then starts your services, automatically injects their dependencies, monitors their health, and reports their status to external http health checks.

Application frameworks add complexity and obscure control flow, but they can also save a lot of time with setup and maintenance tasks. To be sure app-frame is right for your project, see the [Trade-offs](#trade-offs) section.

## Usage

```toml
[dependencies]
app_frame = "0.3.0"
```

App Frame provides macros for convenience. If they feel too esoteric or inflexible, you can alternatively implement traits to wire up your application.

This trivial example illustrates the bare minimum boilerplate to use the framework with its macros, but doesn't actually run anything useful.

```rust
use app_frame::{application, service_manager::Application, Never};

#[tokio::main]
async fn main() -> anyhow::Result<Never> {
    MyApp.run().await
}

pub struct MyApp;

application!(self: MyApp);
```

Here is the equivalent application, with direct trait implementation instead of macros:

```rust
use app_frame::{application, service_manager::Application, Never};

#[tokio::main]
async fn main() -> anyhow::Result<Never> {
    MyApp.run().await
}

pub struct MyApp;

impl Initialize for MyApp {
    fn init(&self) -> Vec<Arc<dyn Job>> {
        vec![]
    }
}

impl Serves for MyApp {
    fn services(&self) -> Vec<Box<dyn Service>> {
        vec![]
    }
}
```


To take full advantage of app-frame, you should define dependency relationships and explicitly declare all the components that you want to run as part of your application.

### Make Components Injectible

Make a struct injectible if you want its dependencies to be injected by app-frame, with either a macro or trait:
- **macro:** Wrap the struct with the `inject!` macro. In a future release, this will be an attribute-style macro.
- **traits:** `impl<T> From<&T> for C where T: Provides<D>` for the component `C` and each of its dependencies `D`.
```rust
/// Macro approach. This automatically implements:
/// impl<T> From<&T> for InitJob 
/// where 
///       T: Provides<Arc<dyn Repository>> + Provides<Component2> {...}
inject!(
    pub struct Component1 {
        repo: Arc<dyn Repository>,
        component2: Component2,
    }
);
```

```rust
/// Trait approach
/// 
/// This is practical here since only one item needs to be injected,
/// and the others can be set to default values. The inject macro
/// does not yet support defaults.
impl<T> From<&T> for MyService
where
    T: Provides<Arc<dyn Repository>>,
{
    fn from(p: &T) -> Self {
        Self {
            repository: p.provide(),
            heartbeat: Arc::new(()),
            my_health_metric: true,
        }
    }
}
```

### Define Services
App Frame is built with the assumption that it will trigger some long running services on startup. To define a long-running service, either implement `Service`, or implement `Job` and `SelfConfiguredLoop`.
```rust
/// Implement Service when you already have some logic that runs forever
#[async_trait]
impl Service for MyService {
    async fn run_forever(&self) -> Never {
        // This is a trivial example. You can call something that you expect to run forever.
        loop {
            self.heartbeat.beat();
        }
    }

    fn heartbeat_ttl(&self) -> i32 {
        60
    }

    fn set_heartbeat(&mut self, heartbeat: Arc<dyn Heartbeat + 'static>) {
        self.heartbeat = heartbeat;
    }

    fn is_healthy(&self) -> bool {
        self.my_health_metric
    }
}
```

```rust
/// Implementing these traits will let you use a job that is expected to terminate
/// after a short time. It becomes a service that runs the job repeatedly.
#[async_trait]
impl Job for JobToLoopForever {
    async fn run_once(&self) -> anyhow::Result<()> {
        self.solve_halting_problem()
    }
}

impl SelfConfiguredLoop for JobToLoopForever {
    fn loop_config(&self) -> LoopConfig {
        LoopConfig {
            delay_secs: 10,
            max_iteration_secs: 20,
        }
    }
}
```


### Declare the app
Define a struct representing your app and populate it with any configuration or singletons.

Any components that are automatically constructed by the framework will be instantiated once for every component that depends on it. If you want a singleton that is created once and reused, it needs to be instantiated manually, like this example.
```rust
pub struct MyApp {
    db_singleton: Arc<DatabaseConnectionPoolSingleton>,
}

impl MyApp {
    pub fn new(database_config: &str) -> Self {
        Self {
            db_singleton: Arc::new(DatabaseConnectionPoolSingleton {
                conn: database_config.into(),
            }),
        }
    }
}
```

### Declare application components.
Many dependency injection frameworks only need you to define your components, and the framework will locate them and create them as needed. App Frame instead prefers explicitness. You must list all application components in a single place, but App Frame will figure out how to wire them together. This is intended to make the framework's control flow easier to follow, though this benefit may be offset by using macros.

- **macro:** List all of your app's components in the `application!` macro as documented below.
- **traits:**
  - `impl Initialize for MyApp`, providing any `Job`s that need to run at startup.
  - `impl Serves for MyApp`, providing any `Service`s that need to run continuously.
  - `impl Provides<D> for MyApp` for each dependency `D` that will be needed either directly or transitively by any job or service provided above.


### Customize Service Orchestration

You can customize monitoring and recovery behavior by starting your app with the `run_custom` method.

```rust
// This is the default config, which is used when you call `MyApp::new().run()`
// See rustdocs for RunConfig and HealthEndpointConfig for more details.
MyApp::new()
    .run_custom(RunConfig {
        log_interval: 21600,
        attempt_recovery_after: 120,
        http_health_endpoint: Some(HealthEndpointConfig {
            port: 3417,
            success_status: StatusCode::OK,
            fail_status: StatusCode::INTERNAL_SERVER_ERROR,
        }),
    })
    .await
```

### Full macro-based example

This example defines and injects various types of components to illustrate the various features provided by the framework. This code actually runs an application, and will respond to health checks indicating that 2 services are healthy.

```rust
use std::sync::Arc;

use async_trait::async_trait;

use app_frame::{
    application,
    dependency_injection::Provides,
    inject,
    service::{Job, LoopConfig, LoopingJobService, SelfConfiguredLoop, Service},
    service_manager::{Application, Heartbeat},
    Never,
};

#[tokio::main]
async fn main() -> anyhow::Result<Never> {
    MyApp::new("db://host").run().await
}

pub struct MyApp {
    db_singleton: Arc<DatabaseConnectionPoolSingleton>,
}

impl MyApp {
    pub fn new(database_config: &str) -> Self {
        Self {
            db_singleton: Arc::new(DatabaseConnectionPoolSingleton {
                conn: database_config.into(),
            }),
        }
    }
}

// Including a type here implements Provides<ThatType> for MyApp.
//
// Struct definitions wrapped in the `inject!` macro get a From<T>
// implementation where T: Provides<U> for each field of type U in the struct.
// When those structs are provided as a component here, they will be constructed
// with the assumption that MyApp impl Provides<U> for each of those U's
//
// All the types provided here are instantiated separately each time they are
// needed. If you want to support a singleton pattern, you need to construct the
// singletons in the constructor for this type and wrap them in an Arc. Then you
// can provide them in the "provided" section by cloning the Arc.
application! {
    self: MyApp

    // init jobs are types implementing `Job` with a `run_once` function that
    // needs to run once during startup.
    // - constructed the same way as a component
    // - made available as a dependency, like a component
    // - wrap in curly braces for custom construction of an iterable of jobs.
    init [
        InitJob
    ]

    // Services are types with a `run_forever` function that needs to run for
    // the entire lifetime of the application.
    // - constructed the same way as a component
    // - made available as a dependency, like a component
    // - registered as a service and spawned on startup.
    // - wrap in curly braces for custom construction of an iterable of
    //   services.
    // - Use 'as WrapperType' if it needs to be wrapped in order to get
    //   something that implements `Service`. wrapping uses WrapperType::from().
    services [
        MyService,
        JobToLoopForever as LoopingJobService,
    ]

    // Components are items that will be provided as dependencies to anything
    // that needs it. This is similar to the types provided in the "provides"
    // section, except that components can be built exclusively from other
    // components and provided types, whereas "provides" items depend on other
    // state or logic.
    // - constructed via Type::from(MyApp). Use the inject! macro on the
    //   type to make this possible.
    // - Use `as dyn SomeTrait` if you also want to provide the type as the
    //   implementation for Arc<dyn SomeTrait>
    components [
        Component1,
        Component2,
        DatabaseRepository as dyn Repository,
    ]

    // Use this when you want to provide a value of some type that needs to either:
    // - be constructed by some custom code you want to write here.
    // - depend on some state that was initialized in MyApp.
    //
    // Syntax: Provide a list of the types you want to provide, followed by the
    // expression that can be used to instantiate any of those types.
    // ```
    // TypeToProvide: { let x = self.get_x(); TypeToProvide::new(x) },
    // Arc<dyn Trait>, Arc<ConcreteType>: Arc::new(ConcreteType::default()),
    // ```
    provided {
        Arc<DatabaseConnectionPoolSingleton>: self.db_singleton.clone(),
    }
}

inject!(
    pub struct InitJob {
        repo: Arc<dyn Repository>,
    }
);

#[async_trait]
impl Job for InitJob {
    async fn run_once(&self) -> anyhow::Result<()> {
        Ok(())
    }
}

inject!(
    pub struct JobToLoopForever {
        c1: Component1,
        c2: Component2,
    }
);

#[async_trait]
impl Job for JobToLoopForever {
    async fn run_once(&self) -> anyhow::Result<()> {
        Ok(())
    }
}

impl SelfConfiguredLoop for JobToLoopForever {
    fn loop_config(&self) -> LoopConfig {
        LoopConfig {
            delay_secs: 10,
            max_iteration_secs: 20,
        }
    }
}

inject!(
    pub struct Component1 {}
);

inject!(
    pub struct Component2 {
        repo: Arc<dyn Repository>,
    }
);

pub trait Repository: Send + Sync {}

pub struct MyService {
    repository: Arc<dyn Repository>,
    heartbeat: Arc<dyn Heartbeat + 'static>,
    my_health_metric: bool,
}

/// This is how you provide a custom alternative to the `inject!` macro, it is
/// practical here since only one item needs to be injected, and the others can
/// be set to default values.
impl<T> From<&T> for MyService
where
    T: Provides<Arc<dyn Repository>>,
{
    fn from(p: &T) -> Self {
        Self {
            repository: p.provide(),
            heartbeat: Arc::new(()),
            my_health_metric: true,
        }
    }
}

#[async_trait]
impl Service for MyService {
    async fn run_forever(&self) -> Never {
        loop {
            self.heartbeat.beat();
        }
    }

    fn heartbeat_ttl(&self) -> i32 {
        60
    }

    fn set_heartbeat(&mut self, heartbeat: Arc<dyn Heartbeat + 'static>) {
        self.heartbeat = heartbeat;
    }

    fn is_healthy(&self) -> bool {
        self.my_health_metric
    }
}

inject!(
    pub struct DatabaseRepository {
        connection: Arc<DatabaseConnectionPoolSingleton>,
    }
);

impl Repository for DatabaseRepository {}

pub struct DatabaseConnectionPoolSingleton {
    conn: String,
}
```

# Trade-offs
Application frameworks are often not worth the complexity, but they do have utility in many cases. App Frame would typically be useful in a complicated backend web service with a lot of connections to other services, or whenever the following conditions are met:

- Multiple fallible long running tasks need to run indefinitely in parallel with monitoring and recovery.
- You want to use tokio's event loop to run suspending functions in parallel.
- The decoupling achieved by dependency inversion is beneficial enough to be worth the complexity of introducing layers of abstraction.
- The application has a complex dependency graph of internal components, and you'd like to make it easier to change the dependency relationships in the future.
- You want to be explicit, in a single place, about every component that should actually be instantiated when the app starts. This is a key differentiator from most other dependency injection frameworks.
- You don't mind gigantic macros that only make sense after reading documentation. Macros are not required to use App Frame, but you can use them to significantly reduce boilerplate.
- You are willing to compromise some performance and readability with the indirection of vtables and smart pointers.