There have been a couple of discussions here on SideWalk about Dependency Injection. My view is simple, I can't live without it! In particular I miss T5-IoC along with the benefits it brings such as distributed configuration and extensive error reporting. For this reason I bring you a preview of:
Browse the docs and give it a whirl! pod.fandoc follows:
Overview
IoC is an Inversion of Control (IoC) container, or Dependency Injection (DI) framework, based on the most excellent Tapestry 5 IoC for Java. Above and beyond all else, this IoC offers:
distributed service configuration between pods and modules
IoC distinguishes between Services and Dependencies.
A service is a singleton class of which there is only one instance (or one per thread). Each service is identified by a unique ID (usually the unqualified class name) and may, or may not, be represented by a Mixin. Services are managed by IoC and must be defined by a module. Services may gather configuration contributed from other modules.
A dependency is any class or object that another class depends on. A dependency may or may not be a service. For example, a class may depend on having a field Int maxNoOfThreads but that Int isn't a service. Non service dependencies may be managed by (future) user defined dependency providers.
A contribution is a means to configure a service.
A module is a class where services and contributions are defined.
Loading Modules
When building a registry, you declare which modules are to loaded. You may also load modules in dependant pods, in which case, each pod should have declared the following meta:
"afIoc.module" : "{qname}"
Where {qname} is a qualified type name of a module. Additional modules can be declared by the @SubModule facet.
Modules can also be loaded from index properties in a similar manner.
Defining Services
Services are defined in Module classes, where each meaningful method is static and annotated with a facet.
The exception is bind() which does not have a facet but is declared with a standard signature. The bind method is the common means to define services, examples below:
static Void bind(ServiceBinder binder) {
// has service ID of 'MyService'
binder.bind(MyService#, MyServiceImpl#)
// has service ID of 'myServiceImpl'
binder.bindImpl(MyServiceImpl#)
// has service ID of 'elephant'
binder.bindImpl(MyServiceImpl#).withId("elephant")
}
Modules may can also define builder methods. These are static methods annotated with the @Build facet. Here you may construct and return the service yourself. Any parameters are taken to be dependencies and are resolved as such when the method is called. For example, to manually build a service with the Id penguin:
Note that under the covers, all services are resolved via their unique service ids, injection by type is merely a layer on top, added for convenience.
When IoC autobuilds a service it locates a suitable ctor. This is either the one donned with the @Inject facet or the one with the most parameters. Ctor parameters are taken to be dependencies and are resolved appropriately.
Field injection happens after the object has been created and so fields must be declared as nullable:
@Inject
MyService? myService
The exception to the rule is if you declare a serialisation ctor:
new make(|This|? f) { f?.call(this) }
On calling f all injectable fields will be set, even fields marked as const.
After object construction and field injection, any extra setup may be performed via methods annotated with @PostInjection. These methods may be of any visibility and all parameters are resolved as dependencies.
Service Scope
Services are either created once perApplication (singletons) or once perThread. Application scoped services must be defined as const. If you need mutable state in your const service, try using the ConcurrentState class.
It is illegal to attempt to inject a perThread scoped service into a perApplication scoped service.
Service Configuration
Services can solicit configuration from modules simply by declaring a list or a map in their ctor or builder method.
class Example {
new make(Str[] mimeTypes) { ... }
...
}
Modules may then contribute to the Example service:
The list and map types are inferred from the ctor definition and all contribution types must fit.
If the service declares a map configuration then contribution methods should take a MappedConfig object. If the map config uses Str as the key, then the created map is caseInsensitive otherwise the map is ordered.
Tips
Define one main module and declare it both the pod meta and the pod index props. Use @SubModule to reference additional dependant modules in the same pod.
If you have no say in how your classes are created (say, when you're using flux) then use the following line to inject dependencies when needed:
(Service.find(IocService#) as IocService).injectIntoFields(this)
When creating GUIs (say, with fwt) then use Registry.autobuild to create your panels, commands and other objects. These aren't services and should not be declared as such, but they do often make use of services.
IoC gives detailed error reporting should something go wrong, nevertheless should you need more, use IocHelper.debugOperation to make IoC give trace level contextual information.
Don't be scared of creating const services! Use ConcurrentState to safely store and access mutable state across thread boundaries.
Have fun!
: )
brianThu 4 Apr 2013
This is really awesome! Thanks for sharing, we definitely need lots of libraries like this for the Fantom community.
SlimerDude Tue 2 Apr 2013
There have been a couple of discussions here on SideWalk about Dependency Injection. My view is simple, I can't live without it! In particular I miss T5-IoC along with the benefits it brings such as distributed configuration and extensive error reporting. For this reason I bring you a preview of:
afIoc :: A Dependency Injection (DI) Framework
Available for download at the usual: http://eggbox.fantomfactory.org/pods/afIoc/
It should be pretty fully featured with:
sys::Service
implementationBrowse the docs and give it a whirl!
pod.fandoc
follows:Overview
IoC is an Inversion of Control (IoC) container, or Dependency Injection (DI) framework, based on the most excellent Tapestry 5 IoC for Java. Above and beyond all else, this IoC offers:
Quick Start
Terminology
IoC distinguishes between Services and Dependencies.
A service is a singleton class of which there is only one instance (or one per thread). Each service is identified by a unique ID (usually the unqualified class name) and may, or may not, be represented by a Mixin. Services are managed by IoC and must be defined by a module. Services may gather configuration contributed from other modules.
A dependency is any class or object that another class depends on. A dependency may or may not be a service. For example, a class may depend on having a field
Int maxNoOfThreads
but thatInt
isn't a service. Non service dependencies may be managed by (future) user defined dependency providers.A contribution is a means to configure a service.
A module is a class where services and contributions are defined.
Loading Modules
When building a registry, you declare which modules are to loaded. You may also load modules in dependant pods, in which case, each pod should have declared the following meta:
"afIoc.module" : "{qname}"
Where
{qname}
is a qualified type name of a module. Additional modules can be declared by the @SubModule facet.Modules can also be loaded from index properties in a similar manner.
Defining Services
Services are defined in Module classes, where each meaningful method is static and annotated with a facet.
The exception is
bind()
which does not have a facet but is declared with a standard signature. The bind method is the common means to define services, examples below:Modules may can also define builder methods. These are static methods annotated with the
@Build
facet. Here you may construct and return the service yourself. Any parameters are taken to be dependencies and are resolved as such when the method is called. For example, to manually build a service with the Idpenguin
:Dependency Injection
IoC performs both ctor and field injection.
Note that under the covers, all services are resolved via their unique service ids, injection by type is merely a layer on top, added for convenience.
When IoC autobuilds a service it locates a suitable ctor. This is either the one donned with the
@Inject
facet or the one with the most parameters. Ctor parameters are taken to be dependencies and are resolved appropriately.Field injection happens after the object has been created and so fields must be declared as nullable:
The exception to the rule is if you declare a serialisation ctor:
On calling
f
all injectable fields will be set, even fields marked asconst
.After object construction and field injection, any extra setup may be performed via methods annotated with
@PostInjection
. These methods may be of any visibility and all parameters are resolved as dependencies.Service Scope
Services are either created once perApplication (singletons) or once perThread. Application scoped services must be defined as
const
. If you need mutable state in your const service, try using the ConcurrentState class.It is illegal to attempt to inject a
perThread
scoped service into aperApplication
scoped service.Service Configuration
Services can solicit configuration from modules simply by declaring a list or a map in their ctor or builder method.
Modules may then contribute to the
Example
service:The list and map types are inferred from the ctor definition and all contribution types must fit.
If the service declares a map configuration then contribution methods should take a
MappedConfig
object. If the map config usesStr
as the key, then the created map iscaseInsensitive
otherwise the map isordered
.Tips
Define one main module and declare it both the pod meta and the pod index props. Use
@SubModule
to reference additional dependant modules in the same pod.If you have no say in how your classes are created (say, when you're using flux) then use the following line to inject dependencies when needed:
When creating GUIs (say, with fwt) then use Registry.autobuild to create your panels, commands and other objects. These aren't services and should not be declared as such, but they do often make use of services.
IoC gives detailed error reporting should something go wrong, nevertheless should you need more, use
IocHelper.debugOperation
to make IoC give trace level contextual information.Don't be scared of creating
const
services! Use ConcurrentState to safely store and access mutable state across thread boundaries.Have fun!
: )
brian Thu 4 Apr 2013
This is really awesome! Thanks for sharing, we definitely need lots of libraries like this for the Fantom community.
SlimerDude Sun 28 Apr 2013
afIoc v1.2.0 now released!
A powerful "Inversion Of Control" (IOC) framework
New noteworthy features include:
In particular the overrides are powerful features for they let users tweak and decorate existing services and change default configuration settings.
See Release Notes for full change details
Have fun!