Dependency Injection

마젠토2 – 의존성 주입(Dependency Injection) 디자인 패턴

PHP 의존성 주입(Dependency Injection, DI) 디자인 패턴의 기본 컨셉

예를들어 A객체 내에서 B객체가 생성된다면 A객체는 B객체에 의존성을 갖게 됩니다. B객체 생성자의 소스코드가 수정된다면 A객체의 소스코드 또한 바뀌어야 하기 때문에 A객체는 B객체에 의존성을 갖게 되는 것입니다. 의존성 주입(Dependency Injection, DI)은 이러한 객체간의 의존성을 제거하는 디자인 패턴입니다. 쉽게 말해서 A객체 내에 B객체가 필요하다면 A객체 내에서 B객체를 생성시키지 않고 외부에서 B객체를 생성시킨후 A객체 안에 주입시키는 것이 의존성 주입(DI)입니다. 의존성 주입(DI)을 사용하게 되면 모듈간의 의존성을 없애기 때문에 좀 더 수월한 유닛테스팅을(unit testing) 할수 있게 해주고 코드의 재사용(code reuse) 및 코드관리에 있어서도 많은 편의성을 제공해 줍니다.

그렇다면 이 의존성 주입이 무엇인지 가장 기본적인 예제를 통해 살펴보도록 하겠습니다. 아래의 코드는 의존성 주입 패턴을 사용하지 않은 코드의 예제입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
class Address
{
    private $_country;

    public function __construct($country)
    {
        $this->_country = $country;
    }

    public function getCountry()
    {
        return $this->_country;
    }
}

class Customer
{
    private $_name;
    private $_address;

    public function __construct($name, $country)
    {
        $this->_name = $name;
        $this->_address = new Address($country);
    }

    public function getName()
    {
        return $this->_name();
    }

    public function getAddress()
    {
        return $this->_address();
    }
}
?>

위 예제의 경우 Customer 객체 안에서 새로운 Address 객체를 생성하기 때문에 Customer 클래스는 Address 클래스에 의존하게 됩니다. 특별히 문제될것은 없어보이지만 만약 Address::__construct() 멤버함수가 변경이 된다면 Customer 클래스에서도 코드 변경을 해야할 경우가 생기기도 합니다.
예를들어 Address 클래스가 아래와 같이 변경된다면 어떨까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Address
{
    private $_city;
    private $_country;

    public function __construct($city, $country)
    {
        $this->_city = $city;
        $this->_country = $country;
    }

    public function getCity()
    {
        return $this->_city();
    }

    public function getCountry()
    {
        return $this->_country();
    }
}
?>

위와같이 Address 클래스의 생성자 변수가 $_city, $_country 두가지로 늘어났기 때문에 Customer 클래스처럼 Address 객체를 생성시키는 모든 클래스를 수정해야 합니다. 위에서 제시한 예제의 경우 Customer 클래스를 아래와 같이 변경시켜야 제대로 작동 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Customer
{
    private $_name;
    private $_address;

    public function __construct($name, $city, $country)
    {
        $this->_name = $name;
        $this->_address = new Address($city, $country);
    }

    public function getName()
    {
        return $this->_name();
    }

    public function getAddress()
    {
        return $this->_address();
    }
}
?>

하지만 이런식으로 Address 클래스의 생성자가 변경될때마다 매번 Customer 클래스를 수정해야 하는것은 불편하고 비효율적 이므로 아예 Address 객체를 주입하는걸 어떨까요? 아래의 예제는 의존성 주입 패턴을 사용한 Customer 클래스 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Customer
{
    private $_name;
    private $_address;

    public function __construct($name, Address $address)
    {
        $this->_name = $name;
        $this->_address = $address;
    }

    public function getName()
    {
        return $this->_name();
    }

    public function getAddress()
    {
        return $this->_address();
    }
}
?>

위와 같이 수정을 해서 Address 객체를 Customer에 생성자의 변수로 직접 주입하게 되면 Address 클래스에서 어떤 수정이 이루어지던 Customer 클래스를 수정할 필요가 없어집니다. 이런 의존성 주입(Dependency Injection, DI) 디자인 패턴은 데모용 프로젝트나 규모가 작은 프로젝트에는 굳이 적용할 필요가 없지만, 프로젝트가 지속적으로 업데이트되고 규모가 커질수록 의존성 주입 패턴은 모듈간의 의존성을 제거시키는데 큰 역할을 하게 되고 소스 코드를 유지 보수 하는데 큰 도움이 됩니다. 같은 이유로 “마젠토2″에서도 “마젠토1″에서는 사용하지 않았던 의존성 주입 패턴을 사용하고 있습니다.

마젠토2″에서 사용된 의존성 주입 – Object Manager 와 di.xml의 사용

마젠토 2 – 의존성 주입 (Dependency Injection)
아래 코드는 “마젠토 2″의 Magento\Catalog\Model\Product::__construct() 멤버함수 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class Product extends \Magento\Catalog\Model\AbstractModel implements
    IdentityInterface,
    SaleableInterface,
    ProductInterface
{
    public function __construct(
        \Magento\Framework\Model\Context $context,
        \Magento\Framework\Registry $registry,
        \Magento\Catalog\Api\ProductAttributeRepositoryInterface $metadataService,
        AttributeDataBuilder $customAttributeBuilder,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        Product\Url $url,
        Product\Link $productLink,
        \Magento\Catalog\Model\Product\Configuration\Item\OptionFactory $itemOptionFactory,
        \Magento\CatalogInventory\Api\Data\StockItemDataBuilder $stockItemBuilder,
        \Magento\Catalog\Model\Product\Option $catalogProductOption,
        \Magento\Catalog\Model\Product\Visibility $catalogProductVisibility,
        \Magento\Catalog\Model\Product\Attribute\Source\Status $catalogProductStatus,
        \Magento\Catalog\Model\Product\Media\Config $catalogProductMediaConfig,
        Product\Type $catalogProductType,
        \Magento\Catalog\Helper\Image $catalogImage,
        \Magento\Framework\Module\Manager $moduleManager,
        \Magento\Catalog\Helper\Product $catalogProduct,
        Resource\Product $resource,
        Resource\Product\Collection $resourceCollection,
        \Magento\Framework\Data\CollectionFactory $collectionFactory,
        \Magento\Framework\Filesystem $filesystem,
        \Magento\Indexer\Model\IndexerRegistry $indexerRegistry,
        \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
        \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
        \Magento\Catalog\Model\Indexer\Product\Eav\Processor $productEavIndexerProcessor,
        CategoryRepositoryInterface $categoryRepository,
        array $data = []
    ) {
        $this->_itemOptionFactory = $itemOptionFactory;
        $this->_stockItemBuilder = $stockItemBuilder;
        $this->_optionInstance = $catalogProductOption;
        $this->_catalogProductVisibility = $catalogProductVisibility;
        $this->_catalogProductStatus = $catalogProductStatus;
        $this->_catalogProductMediaConfig = $catalogProductMediaConfig;
        $this->_catalogProductType = $catalogProductType;
        $this->_catalogImage = $catalogImage;
        $this->moduleManager = $moduleManager;
        $this->_catalogProduct = $catalogProduct;
        $this->_collectionFactory = $collectionFactory;
        $this->_urlModel = $url;
        $this->_linkInstance = $productLink;
        $this->_filesystem = $filesystem;
        $this->indexerRegistry = $indexerRegistry;
        $this->_productFlatIndexerProcessor = $productFlatIndexerProcessor;
        $this->_productPriceIndexerProcessor = $productPriceIndexerProcessor;
        $this->_productEavIndexerProcessor = $productEavIndexerProcessor;
        $this->categoryRepository = $categoryRepository;
        parent::__construct(
            $context,
            $registry,
            $metadataService,
            $customAttributeBuilder,
            $storeManager,
            $resource,
            $resourceCollection,
            $data
        );
    }
}

위의 “마젠토2” 코드는 확실히 복잡해 보이며 아마도 “new” 를 사용하거나 기존의 마젠토에서 사용하던 방법으로 객체를 생성하는 일은 거의 불가능해 보입니다.
하지만 “마젠토2″에서는 모든 객체를 관리하는 “Object Manager”(Magento\App\ObjectManager)가 새롭게 생겼구요 모든 객체의 생성은 이 “Object Manger”를 통해서 생성됩니다.
기존의 마젠토에서 아래같은 코드를 사용해서 product 객체를 생성했다면,

1
$product = Mage::getModel('catalog/product');

마젠토2 에서는 아래의 코드처럼 ObjectManager를 사용해서 product 객체를 생성하게 됩니다.

1
$product = $this->_objectManager->get('Magento\Catalog\Model\Product');

그리고 “Object Manager”와 함께 “di.xml” 파일도 새롭게 생겼는데 파일 이름 자체가 Dependency Injection(DI)에서 따온걸로 보이는 이 파일은 Object Manager에 필요한 모듈이나 객체의 의존성 주입 관련 설정을 담고있구요, 애플리케이션이나 모듈의 레벨에 따라 다른 위치에 저장됩니다.
– 애플리케이션의 글로벌 설정일 경우: app/etc/di.xml
– 모듈의 글로벌 설정일 경우: {module}/etc/di.xml
– 모듈의 특정 지역 설정일 경우: {module}/etc/{areaCode}/di.xml

제가 처음으로 마젠토를 접했을때 다른 e-commerce 플랫폼보다 좀 복잡하다는 느낌을 받았는데 마젠토2 또한 새로운 컨셉이나 기능들이 더 생겨나서 적응하는데 시간이 좀 걸릴듯 합니다. 계획대로라면 마젠토2는 내년 4분기에나 접할수 있을것으로 보이지만 기본적인 컨셉은 미리미리 알아두는게 좋을듯 합니다. 게다가 의존성 주입(Dependency Injection)같이 소프트웨어 개발에서 자주 쓰이는 디자인 패턴은 굳이 마젠토 때문이 아니라도 알아두면 좋구요, 마젠토 개발자라면 마젠토2의 Object Manager나 dl.xml같이 새롭지만 핵심적인 개념/기능들을 숙지하고 있어야 할 것 같습니다.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *