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
name : String- The name that should be used for the service. Defaults to the type's name snake cased.public : Bool- If the service should be directly accessible from the container. Defaults tofalse.tags : Array(String)- Tags that should be assigned to the service. Defaults to an empty array.
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