Skip to content

module Athena::DependencyInjection #

Athena's Dependency Injection (DI) component, ADI for short, adds a service container layer to your project. This allows useful objects, aka services, to be shared throughout the project. These objects live in a special class called the ADI::ServiceContainer (SC).

The SC is lazily initialized on fibers; this allows the SC to be accessed anywhere within the project. The Athena::DependencyInjection.container method will return the SC for the current fiber. Since the SC is defined on fibers, it allows for each fiber to have its own SC instance. This can be useful for web frameworks as each request would have its own SC scoped to that request.

Tip

It is highly recommended to use interfaces as opposed to concrete types when defining the initializers for both services and non-services.

Using interfaces allows changing the functionality of a type by just changing what service gets injected into it, such as via an alias. See this blog post for an example of this.

Class methods#

.container : ADI::ServiceContainer #

Returns the ADI::ServiceContainer for the current fiber.

View source

Macros#

auto_configure(type, options) #

Applies the provided options to any registered service of the provided type.

A common use case of this would be to apply a specific tag to all instances of an interface; thus preventing the need to manually apply the tag for each implementation. This can be paired with Athena::DependencyInjection.bind to make working with tags easier.

Example#
module ConfigInterface; end

# Automatically apply the `"config"` tag to all instances of `ConfigInterface`.
ADI.auto_configure ConfigInterface, {tags: ["config"]}

@[ADI::Register]
record ConfigOne do
  include ConfigInterface
end

@[ADI::Register]
record ConfigTwo do
  include ConfigInterface
end

# Options supplied on the annotation itself override the auto configured options.
@[ADI::Register(tags: [] of String)]
record ConfigThree do
  include ConfigInterface
end

@[ADI::Register(_configs: "!config", public: true)]
record ConfigClient, configs : Array(ConfigInterface)

ADI.container.config_client.configs # => [ConfigOne(), ConfigTwo()]
View source

bind(key, value) #

Allows binding a value to a key in order to enable auto registration of that value.

Bindings allow scalar values, or those that could not otherwise be handled via service aliases, to be auto registered. This allows those arguments to be defined once and reused, as opposed to using named arguments to manually specify them for each service.

Bindings can also be declared with a type restriction to allow taking the type restriction of the argument into account. Typed bindings are always checked first as the most specific type is always preferred. If no typed bindings match the argument's type, then the last defined untyped bindings is used.

Example#
module ValueInterface; end

@[ADI::Register(_value: 1, name: "value_one")]
@[ADI::Register(_value: 2, name: "value_two")]
@[ADI::Register(_value: 3, name: "value_three")]
record ValueService, value : Int32 do
  include ValueInterface
end

# Untyped bindings
ADI.bind api_key, ENV["API_KEY"]
ADI.bind config, {id: 12_i64, active: true}
ADI.bind static_value, 123
ADI.bind odd_values, ["@value_one", "@value_three"]
ADI.bind value_arr, [true, true, false]

# Typed bindings
ADI.bind value_arr : Array(Int32), [1, 2, 3]
ADI.bind value_arr : Array(Float64), [1.0, 2.0, 3.0]

@[ADI::Register(public: true)]
record BindingClient,
  api_key : String,
  config : NamedTuple(id: Int64, active: Bool),
  static_value : Int32,
  odd_values : Array(ValueInterface)

@[ADI::Register(public: true)]
record IntArr, value_arr : Array(Int32)

@[ADI::Register(public: true)]
record FloatArr, value_arr : Array(Float64)

@[ADI::Register(public: true)]
record BoolArr, value_arr : Array(Bool)

ADI.container.binding_client # =>
# BindingClient(
#  @api_key="123ABC",
#  @config={id: 12, active: true},
#  @static_value=123,
#  @odd_values=[ValueService(@value=1), ValueService(@value=3)])

ADI.container.int_arr   # => IntArr(@value_arr=[1, 2, 3])
ADI.container.float_arr # => FloatArr(@value_arr=[1.0, 2.0, 3.0])
ADI.container.bool_arr  # => BoolArr(@value_arr=[true, true, false])
View source