#atom #scope #context #framework #cdi

natom

A minimal CDI framework for rust

3 releases

Uses old Rust 2015

0.1.1-rc.2 Jun 29, 2023
0.1.0 Jun 28, 2023

#39 in #atom

MIT license

12KB
125 lines

Natom

crates.io version crates.io downloads

Natom is a minimal CDI implementation in rust.


lib.rs:

This crate is a minimal CDI implementation for rust. While the CDI implementation is not standard conformant in any way, it has some resemblence to WELD.

CDI introduction

CDI is a broad topic, and not easily covered.

Atoms

Atoms (or in WELD, beans) are the cornerstone of the CDI system. An atom is simply a managed struct; managed meaning that construction and destruction is handled by the context. In a big application, you may have multiple application-wide objects you want to pass around and inject (which in reality just means use in someone's constructor). This can become very tedious. Atoms are suitable for both (singleton) application-wide objects, but also creating new instances.

Atoms are created by annotating a struct with [macro@atom].

#[atom]
struct Foo {}

Context

Atoms are created inside a context.

Scopes

Every atom has a specified scope. Scope refers to how the atom is constructed and reused. The behaviour of injections depends on the scope of the atom. The following scopes are implemented:

  • Entrypoint:
  • Singleton:
  • Dependent:

Relations & Inject

When building a service-oriented system, one might split the logic into many different objects, each having some relation to the other but also being a logical unit in-and-off itself. As such, we need a way to represent relations.

#[atom]
struct FooService {
    #[inject]
    repository: FooRepository,
}

#[atom]
struct FooRepository {}

We do this by using the [macro@inject] macro. We simply inject an instance into our own constructor. The instance could be shared between different FooService instances, or unique, depending on the scope of FooRepository.

Produces

Sometimes, we want to inject objects which are not atoms. This could be third-party dependencies, but also random numbers.

We also want to be able to name the alternative of the producer.

#[atom]
struct IntProducer {
    
}

impl IntProducer {
    #[produces]
    fn make_int() i32 {
        0
    }
}

#[atom]
struct Foo {
    #[inject]
    number: i32,
}

Lifetime management

Sometimes, it's not enough to inject all dependencies at construction, or just remove the object at destruction. One might need to close files, sockets, or any other persistance. For this, there is functionality to specify behaviour inside custom functions annotated [macro@construct], [macro@destruct].

Builtin atoms

While the core CDI implementation is useful, there are some parts relevant to the whole ecosystem.

Configuration

For configuration, we use something called configuration properties. Fields annotated with [macro@config_property] are automatically marked with [macro@inject]. [macro@config_property] takes a name of a configuration property when injecting, and looks up that property at runtime. The property can be defaulted and loaded from ENV.

#[atom]
struct Foo {
    #[config_property(foo.speed)]
    speed: f32,
}

The types that [macro@config_property] accepts are:

  • [String]
  • Integers ([i8], [i16], [u8], [u16], ...)
  • Floats ([f32], [f64])

Scheduling

For scheduling tasks ...

Example application

#[atom]
struct Foo {
}

#[atom]
struct Bar {

}

define_base_atoms!();

fn main() {
    let context = Context::new(BaseAtomRegistry {});
    context.start();
}

Dependencies

~230–670KB
~16K SLoC