annotation Athena::DependencyInjection::Register

Overview

Stores metadata associated with a specific service.

The type of the service affects how it behaves within the container. When a struct service is retrieved or injected into a type, it will be a copy of the one in the SC (passed by value). This means that changes made to it in one type, will NOT be reflected in other types. A class service on the other hand will be a reference to the one in the SC. This allows it to share state between types.

Fields

Examples

Without Arguments

If the service doesn't have any arguments then the annotation can be applied without any extra options.

@[ADI::Register]
class Store
  include ADI::Service

  property uuid : String? = nil
end

Multiple Services of the Same Type

If multiple ADI::Register annotations are applied onto the same type, multiple services will be registered based on that type. The name of each service must be explicitly set, otherwise only the last annotation would work.

@[ADI::Register("GOOGLE", "Google", name: "google")]
@[ADI::Register("FACEBOOK", "Facebook", name: "facebook")]
struct FeedPartner
  include ADI::Service

  getter id : String
  getter name : String

  def initialize(@id : String, @name : String); end
end

Service Dependencies

Services can be injected into another service by providing the name of the service as a string, prefixed with an @ symbol. This syntax also works within arrays if you wished to inject a static set of services.

@[ADI::Register]
class Store
  include ADI::Service

  property uuid : String? = nil
end

@[ADI::Register("@store")]
struct SomeService
  include ADI::Service

  def initialize(@store : Store); end
end

Optional Dependencies

Services can be defined with optional dependencies by providing the name of the service as a string, prefixed with an @? symbol. Optional dependencies will supply nil to the initializer versus raising a compile time error if that service does not exist.

@[ADI::Register("@?logger")]
# Defines an optional dependency for the `logger` service.
class Example
  include ADI::Service

  def initialize(logger : Logger?)
    @logger = logger
    # You could also instantiate another type if the ivar should remain not nilable
    # @logger = logger || SomeDefaultLogger.new
  end
end

Tagged Services

Services can be injected into another service based on a tag by prefixing the name of the tag with an ! symbol. This will provide an array of all services that have that tag. It is advised to use this with a parent type/interface to type the ivar with.

NOTE The parent type must also include ADI::Service.

abstract class SomeParentType
  include ADI::Service
end

@[ADI::Register(tags: ["a_type"])]
class SomeTypeOne < SomeParentType
  include ADI::Service
end

@[ADI::Register(tags: ["a_type"])]
class SomeTypeTwo < SomeParentType
  include ADI::Service
end

@[ADI::Register("!a_type")]
struct SomeService
  include ADI::Service

  def initialize(@types : Array(SomeParentType)); end
end

Redefining Services

Services can be redefined by registering another service with the same name. The last defined service with that name will be used. This allows a custom implementation of a service to be used as a dependency to another service, or for injection into a non service type.

@[ADI::Register]
# The original ErrorRenderer, which could originate from an external shard.
record ErrorRenderer, value : Int32 = 1 do
  include ADI::Service
  include ErrorRendererInterface
end

@[ADI::Register(name: "error_renderer"]
# The redefined service, any references to `error_renderer`, or `ErrorRendererInterface` will now resolve to `CustomErrorRenderer`.
record CustomErrorRenderer, value : Int32 = 2 do
  include ADI::Service
  include ErrorRendererInterface
end

Defined in:

athena-dependency_injection.cr