Posts

마젠토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같이 새롭지만 핵심적인 개념/기능들을 숙지하고 있어야 할 것 같습니다.

마젠토 2 미리보기

“마젠토 2″가 개발중에 있다는 얘기는 지속적으로 나왔고 언제쯤에나 정식 버전이 나오려나 했는데, 결국 2015년 4분기 쯤에는 누구나 “마젠토 2” 커뮤니티 버전을 사용할수 있을것 같습니다. 지난주에 있었던 Magento 2 Developer Beta Webinar에 참여해서 베타버전에 대한 간략한 설명을 들었는데요, 여전히 개발중에 있고 오픈소스의 장점을 살려 많은 개발자들의 참여를 통해 발전시켜 나가고 싶다는 이야기를 들었습니다.

오픈소스
현재 개발자를 위한 마젠토 2 사이트는 http://magento.com/developers/magento2에 있습니다. 소스코드는 여전히 개발중에 있어 완벽하지는 않지만 개발자들을 위해 베타 버전의 소스가 Github에 공개되어있습니다.

https://github.com/magento/magento2/
누구나 코드를 다운로드 받아서 또는 Git을 통해 직접 서버에 설치해서 사용해 볼 수 있구요, 부족한 부분을 발견하시면 직접 코드기여도 할수 있습니다. github에 익숙하신 분들은 repository를 fork 한 후에 pull request를 보내면 되구요, github이 익숙치 않은 분들은 아래 두권의 Android용 e-book을 참조하시기 바랍니다.
두권다 모두 공짜입니다.


“마젠토 2″에서 향상된 점

기본적으로 “마젠토 2″는 PHP 5.5(PHP 5.4.11이 최소한의 사양)와 MySQL 5.6의 사용이 가능하고, 그외에 jQuery, CSS3, HTML5, RequireJS등이 기본적으로 사용되고 있습니다. “마젠토 1″보다 “마젠토 2″에서 나아진 점은:

  • 쉬워진 설치와 업그레이드
  • 향상된 인덱서와 Full Page Caching – 퍼포먼스의 향상
  • “마젠토 1″에서 약간의 문제가 되었던 모델간의 dependency 감소 – Dependency Injection 사용
  • 향상된 검색기능과 검색기능의 늘어난 확장성
  • 더 발전된 API로 인해 수월해진 3rd party system과 연동성
  • “마젠토 1″에서는 없었던 XML validation 기능
  • Frontend 개발자를 위한 blank template
  • Frontend 템플릿에서 완전 분리된 비지니스 로직

등등 이라고 합니다. 아직 베타버전이어서 앞으로 많은것들이 더해지고 수정될 예정이지만 2014년 12월 현재로서는 기본적인 프레임워크와 Frontend 정도만 준비되어 있는걸로 보입니다.

기존 마젠토에서 “마젠토 2″로 업그레이드
기존에 “마젠토 1″을 사용하시던 분들은 아쉽게도 “마젠토 2″로 업그레이드가 그리 쉽지만은 않을것으로 보입니다. 대폭 향상된 구조때문에 기존에 사용하던 모듈이나 템플렛은 “마젠토 2″에서 사용할수 없겠지만, 제품, 카테고리, 손님 데이터는 import/export를 통해 옮길수 있습니다.

관리자 모드
실제로 서버에 마젠토 2 베타버전을 설치해 보았는데 관리자 모드는 확실히 더 깔끔해지고 세련된 느낌입니다. 아무 데이터도 없는 상태의 관리자 모드 스크린샷 몇개를 첨부했습니다. 마젠토 버전은 ver.0.42.0-beta1 입니다.

유용한 링크: