Dependency Injection é um termo criado por Martin Fowler e defensores do princípio da inversão de controle (IoC) para caracterizar como classes obtém suas depedências.
class RealBillingService implements BillingService
{
public function chargeOrder(Order $order, PaymentMethod $paymentMethod) : Receipt
{
$logger = new Logger();
$processor = new RealPaymentProcessor();
try {
$result = $processor->charge($order, $paymentMethod);
return $result->wasSuccessful()
? Receipt::forSuccessfulCharge($order->getAmount())
: Receipt::forDeclinedCharge($result->getDeclineMessage());
} catch (ConnectionError $exception) {
$logger->error($exception->getMessage(), ['exception' => $exception]);
return Receipt::forSystemFailure($exception->getMessage());
}
}
}
Problemas
class RealBillingService implements BillingService
{
private $logger;
private $paymentProcessor;
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function setPaymentProcessor(PaymentProcessor $paymentProcessor)
{
$this->paymentProcessor = $paymentProcessor;
}
public function chargeOrder(Order $order, PaymentMethod $paymentMethod) : Receipt
{
try {
$result = $this->paymentProcessor->charge($order, $paymentMethod);
return $result->wasSuccessful()
? Receipt::forSuccessfulCharge($order->getAmount())
: Receipt::forDeclinedCharge($result->getDeclineMessage());
} catch (ConnectionError $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return Receipt::forSystemFailure($exception->getMessage());
}
}
}
Problemas
class RealBillingService implements BillingService
{
public $logger;
public $paymentProcessor;
public function chargeOrder(Order $order, PaymentMethod $paymentMethod) : Receipt
{
try {
$result = $this->paymentProcessor->charge($order, $paymentMethod);
return $result->wasSuccessful()
? Receipt::forSuccessfulCharge($order->getAmount())
: Receipt::forDeclinedCharge($result->getDeclineMessage());
} catch (ConnectionError $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return Receipt::forSystemFailure($exception->getMessage());
}
}
}
$billingService = new RealBillingService();
$billingService->logger = new Logger();
$billingService->paymentProcessor = new PaymentProcessor();
Problemas
>>> from unittest.mock import patch
>>> @patch('module.ClassName2')
... @patch('module.ClassName1')
... def test(MockClass1, MockClass2):
... module.ClassName1()
... module.ClassName2()
... assert MockClass1 is module.ClassName1
... assert MockClass2 is module.ClassName2
... assert MockClass1.called
... assert MockClass2.called
...
>>> test()
class RealBillingService implements BillingService
{
private $logger;
private $paymentProcessor;
public function __construct(
LoggerInterface $logger,
PaymentProcessor $paymentProcessor
) {
$this->logger = $logger;
$this->paymentProcessor = $paymentProcessor;
}
public function chargeOrder(Order $order, PaymentMethod $paymentMethod) : Receipt
{
try {
$result = $this->paymentProcessor->charge($order, $paymentMethod);
return $result->wasSuccessful()
? Receipt::forSuccessfulCharge($order->getAmount())
: Receipt::forDeclinedCharge($result->getDeclineMessage());
} catch (ConnectionError $exception) {
$this->logger->error($exception->getMessage(), ['exception' => $exception]);
return Receipt::forSystemFailure($exception->getMessage());
}
}
}
Um container de injeção de dependência é um objeto que sabe como instanciar e configurar objetos. Para saber como fazer este trabalho, ele precisa saber sobre os argumentos de construção e o relacionamento entre os objetos. Fabien Potencier
Problemas com containers
Bibliotecas de injeção de dependência
parameters:
# ...
mailer.transport: sendmail
services:
mailer:
class: Mailer
arguments: ['%mailer.transport%']
newsletter_manager:
class: NewsletterManager
calls: [setMailer, ['@mailer']]
serviceA:
class: ServiceA
arguments: ['Foo', 'Bar']
serviceB:
class: ServiceB
arguments: ['Foo', 'Bar']
serviceC:
class: ServiceC
arguments: ['Foo', 'Bar']
serviceD:
class: ServiceD
arguments: ['Foo', 'Bar']
serviceE:
class: ServiceE
arguments: ['Foo', 'Bar']
serviceF:
class: ServiceF
arguments: ['Foo', 'Bar']
Bibliotecas de injeção de dependência
<!--?xml version="1.0" encoding="UTF-8"?-->
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
Bibliotecas de injeção de dependência
Bibliotecas de injeção de dependência
(Composition Root)
A raiz de composição é, preferencialmente, o único lugar em uma aplicação onde módulos são compostos juntos.
$router->post('/order', ['uses' => 'Domain\Order\Http\OrderController@create']);
(Pure DI)
DI é um conjunto de princípios e padrões; DI Containers são bibliotecas auxiliares opcionais.
$router->post('/order', function () {
return new OrderController(
new RealOrderService()
);
});
abstract class Dependencies {
private $environment;
function __construct(Environment $environment) {
$this->environment = $environment;
}
public function OrderService(): OrderService {
return new OrderService($this->OrderRepository());
}
protected function OrderRepository(): OrderRepository {
return $this->MySQLOrderRepository();
}
protected function MySQLOrderRepository(): MySQLOrderRepository {
return new MySQLOrderRepository($this->DBConnection());
}
protected function DBConnection(): DBConnection {
$environment = $this->environment;
return new DBConnection($environment->dbHost, $environment->dbUsername);
}
}
final class ProductionDependencies extends Dependencies
{
// dependencias de produção
}
final class StagingDependencies extends Dependencies
{
// dependencias de stage
}
final class TestingDependencies extends Dependencies
{
// dependencias de teste
protected function OrderRepository (): OrderRepository {
return $this->InMemoryOrderRepository();
}
}
final class Router
{
private $dependencies;
public function __construct(Dependencies $dependencies) {
$this->dependencies = $dependencies;
}
public function route(Request $request)
{
if ($request->endpoint === '/order') {
return $this->dependencies->OrderService()->createFromRequest($request);
}
}
}