Dependency Injection container for Dart server-side applications.
Update your pubspec.yaml
with:
dependencies:
corsac_di:
git: https://github.com/corsac-dart/di.git
Pub package will be added as soon as API is "stable enough".
DIContainer
supports auto-wiring, which means it will attempt to resolve
entries based on type information:
import 'package:corsac_di/corsac_di.dart';
class FooService {
final BarService bar;
FooService(this.bar);
}
class BarService {
void baz() {
print('foo bar baz');
}
}
void main() {
var container = new DIContainer();
FooService service = container.get(FooService);
service.bar.baz(); // prints 'foo bar baz'
}
With zero configuration needed this covers a good portion of common use cases.
For a more complex and modular applications we usually need more flexibility
and DIContainer
provides a way to configure itself.
Configurations for DIContainer
are standard Dart's Map
objects. Keys
in a configuration map refer to container entries and values can either be
actual values to associate with particular key or a special definition
object which carries information necessary to get the actual value.
Simplest configuration object with static values can look like this:
Map config = {
'mysql.hostname': 'localhost',
'mysql.port': 3306
};
var container = new DIContainer.build([config]);
print(container.get('mysql.hostname')); // prints 'localhost'
It is very common for projects to use environment variables to store sensitive
configuration data. One can use DI.env()
helper to associate container entry
with the value of environment variable:
// say, MYSQL_PASSWORD=123456
Map config = {
'mysql.password': DI.env('MYSQL_PASSWORD'),
};
var container = new DIContainer.build([config]);
print(container.get('mysql.password')); // prints '123456'
This helper also supports dotenv package.
We already saw example of getting an object in the "auto-wiring" section. However following example will result in an error:
class MySQL {
final String host;
final String user;
final String password;
MySQL(this.host, this.user, this.password);
}
var container = new DIContainer();
container.get(MySQL); // will throw DIError
There is no way for container to know actual values of host
, user
and
password
parameters. But we can tell container where to find these values:
Map config = {
MySQL: DI.object()
..bindParameter('host', 'localhost') // binds static value to `host` parameter
..bindParameter('user', DI.env('MYSQL_USER')) // binds value of env variable
..bindParameter('password', DI.env('MYSQL_PASSWORD')) // another env variable
};
var container = new DIContainer.build([config]);
var mysql = container.get(MySQL); // returns instance of `MySQL` class
mysql.query(); // do work
It is also possible to customize which constructor should be called to get new instance:
Map config = {
MySQL: DI.object()
..constructor = 'connect'
// ...bind necessary parameters
};
Here is a typical situation:
abstract class LogHandler {}
class EmailLogHandler implements LogHandler {} // sends email notifications
class NullLogHandler implements LogHandler {} // silently ignores everything
One might want to use NullLogHandler
when running tests to avoid unnecessary
emails being sent in test environment. This can be resolved using DI.get()
helper:
var config = {
LogHandler: DI.get(NullLogHandler),
};
var container = new DIContainer.build([config]);
container.get(LogHandler); // returns instance of `NullLogHandler`
There is a way to define parametrized string which value is resolved based on container configuration. Parameters in a string must be enclosed in curly braces and refer to another container entry:
var config = {
'env': 'prod',
'mysql.database': DI.string('{env}_blog'),
};
var container = new DIContainer.build([config]);
print(container.get('mysql.database')); // prints 'prod_blog'
Any Iterable
entry in the DIContainer
is treated specially. If it only
contains static values then it will be returned as is:
var config = {
'fruits': ['apple', 'pear', 'orange']
};
var container = new DIContainer.build([config]);
print(container.get('fruits')); // prints ['apple', 'pear', 'orange']
However if values contain any of resolvers returned by DI.get()
, DI.object()
,
DI.env()
or DI.string()
, then such values are automatically resolved:
var config = {
'env': 'prod',
'entries': [
DI.env('MYSQL_PASSWORD'), // say MYSQL_PASSWORD=123456
DI.string('{env}_blog'),
DI.get(MySQL),
]
};
var container = new DIContainer.build([config]);
print(container.get('entries'));
// prints ['123456', 'prod_blog', 'Instance of <MySQL>']
In addition, there is DI.add()
helper which allows to dynamically add items
to such lists. This allows for building applications with complex structure
which can be split into a set of modules. Each module in turn can provide
it's own configurations and add items to such lists.
// Base configuration
var baseConfig = {
'migrations': new List(), // list containing all database migrations
};
// MySQL module configuration
var mysqlConfig = {
'migrations': DI.add([
DI.get(MySQLMigration) // adds MySQLMigration to the list
]),
};
// Mongo module configuration
var mongoConfig = {
'migrations': DI.add([
DI.get(MongoMigration) // adds MongoMigration to the list
]),
};
// `DIContainer.build()` accepts a list of configuration objects
var container = new DIContainer.build([
baseConfig,
mysqlConfig,
mongoConfig
]);
print(container.get('migrations'));
// prints [Instance of <MySQLMigration>, Instance of <MongoMigration>]
Public API of this library is inspired by PHP-DI library.
BSD-2