Magento 2 là một hệ thống cho phép lập trình bên thứ ba có thể tùy chỉnh và ghi đè lên core của nó.
Mặc dù điều này sẽ giúp chúng ta dễ dàng tùy biến hơn, nhưng lại gặp phải vấn đề khi có sự thay đổi của Magento thì sẽ ảnh hưởng tới module bên thứ ba.
Để giải quyết vấn đề này thì việc áp dụng nguyên lý Dependency Inversion, cụ thể là mô hình Service Contract là vô cùng hợp lý.
Dependency Inversion và Service Contract là hai khái niệm quan trọng trong kiến trúc của Magento 2.
Đây là một trong những nguyên lý rất phổ biến khi thiết kế một phần mềm rất nổi tiếng hiện nay đó là SOLID bao gồm:
- Single responsibility principle (SRP)
- Open/Closed principle (OCP)
- Liskov substitution principe (LSP)
- Interface segregation principle (ISP)
- Dependency inversion principle (DIP)
Dependency Inversion là gì?
Dependency Inversion (đảo ngược phụ thuộc) là một nguyên tắc thiết kế phần mềm quan trọng trong đó lớp phụ thuộc không được phụ thuộc vào lớp chủ động mà ngược lại, lớp chủ động sẽ phụ thuộc vào lớp phụ thuộc.
Trong Magento 2, nguyên tắc này được áp dụng bằng cách sử dụng Dependency Injection (DI) để quản lý phụ thuộc giữa các lớp trong hệ thống.
Để tìm hiểu chi tiết hơn về Dependency Injection trong lập trình Magento thì chúng ta tìm hiểu bài viết này: [Magento 2] Dependency Injection là gì? Cách thức hoạt động của Dependency Injection (Lập trình Magento 2)
DI là một kỹ thuật cho phép chúng ta loại bỏ sự phụ thuộc cứng giữa các đối tượng và thay vào đó, chúng ta có thể chèn các đối tượng cần thiết vào các đối tượng khác thông qua constructor, setter hoặc các phương thức khác.
Service Contract là gì?
Service Contract là một khái niệm quan trọng khác trong Magento 2, đó là một tập hợp các interface và các lớp triển khai của chúng, được sử dụng để tương tác với các module khác trong hệ thống.
Service Contract định nghĩa các hành động được thực hiện bởi một module cụ thể và các tham số và kết quả được sử dụng để thực hiện các hành động đó.
Các Service Contract được sử dụng để trừu tượng hóa các hành động cần thiết của các module và đảm bảo tính tương thích giữa các module khác nhau.
Lợi ích của Dependency Inversion và Service Contract trong Magento 2
Sử dụng Dependency Inversion và Service Contract trong Magento 2 giúp cho các module trở nên dễ dàng hơn để kiểm tra, bảo trì và mở rộng.
Nó cũng đảm bảo tính tương thích giữa các module khác nhau và giảm thiểu sự phụ thuộc cứng giữa chúng.
Ví dụ:
Để hiểu rõ hơn về Service Contract trong Magento 2, chúng ta có thể xem ví dụ về cách triển khai Service Contract trong một module:
Interface (contract – hợp đồng)
1. Đầu tiên, ta sẽ tạo ra một interface để định nghĩa các hành động cần thiết cho module của mình, chẳng hạn như sau:
<?php
namespace Vendor\Module\Api;
interface ProductRepositoryInterface
{
/**
* @param int $id
* @return \Vendor\Module\Api\Data\ProductInterface
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getById($id);
/**
* @param \Vendor\Module\Api\Data\ProductInterface $product
* @return \Vendor\Module\Api\Data\ProductInterface
* @throws \Magento\Framework\Exception\CouldNotSaveException
*/
public function save(\Vendor\Module\Api\Data\ProductInterface $product);
}
Model (Details – Implement – người thực hiên)
2. Tiếp theo, chúng ta sẽ tạo ra một lớp triển khai cho interface này, ví dụ như sau:
<?php
namespace Vendor\Module\Model;
use Vendor\Module\Api\Data\ProductInterface;
use Vendor\Module\Api\ProductRepositoryInterface;
use Vendor\Module\Model\ResourceModel\Product as ResourceProduct;
use Vendor\Module\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
class ProductRepository implements ProductRepositoryInterface
{
/**
* @var ResourceProduct
*/
private $resource;
/**
* @var ProductFactory
*/
private $productFactory;
/**
* @var ProductCollectionFactory
*/
private $productCollectionFactory;
public function __construct(
ResourceProduct $resource,
ProductFactory $productFactory,
ProductCollectionFactory $productCollectionFactory
) {
$this->resource = $resource;
$this->productFactory = $productFactory;
$this->productCollectionFactory = $productCollectionFactory;
}
/**
* @inheritDoc
*/
public function getById($id)
{
$product = $this->productFactory->create();
$this->resource->load($product, $id);
if (!$product->getId()) {
throw new NoSuchEntityException(__('The product with the "%1" ID doesn\'t exist.', $id));
}
return $product;
}
/**
* @inheritDoc
*/
public function save(ProductInterface $product)
{
try {
$this->resource->save($product);
} catch (\Exception $exception) {
throw new CouldNotSaveException(__($exception->getMessage()));
}
return $product;
}
}
3. Chúng ta sẽ tạo ra một Model khác để cung cấp các hành động được định nghĩa trong interface cho các module khác sử dụng. Ví dụ như sau:
<?php
namespace Vendor\Module\Model;
use Vendor\Module\Api\Data\ProductInterface;
use Vendor\Module\Api\ProductRepositoryInterface;
class ProductManagement implements \Vendor\Module\Api\ProductManagementInterface
{
/**
* @var ProductRepositoryInterface
*/
private $productRepository;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
/**
* @inheritDoc
*/
public function getProductById($id)
{
return $this->productRepository->getById($id);
}
}
Khởi tạo Interface cho ProductManagement
<?php
namespace Vendor\Module\Api;
interface ProductManagementInterface
{
/**
* @param int $id
* @return \Vendor\Module\Api\Data\ProductInterface
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getProductById($id);
}
File mapping – Quy định Service do ai thực hiện:
4. Ta sẽ tạo ra một file XML khác để định nghĩa các cấu hình cần thiết cho module của mình, chẳng hạn như sau:
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<preference for="Vendor\Module\Api\ProductRepositoryInterface" type="Vendor\Module\Model\ProductRepository"/>
<virtualType name="Vendor\Module\Api\ProductManagementInterface" type="Vendor\Module\Model\ProductManagement">
<arguments>
<argument name="productRepository" xsi:type="object">Vendor\Module\Api\ProductRepositoryInterface</argument>
</arguments>
</virtualType>
</config>
Ở đây, chúng ta sử dụng phần tử preference
để xác định rằng interface ProductRepositoryInterface
nên được đối tượng hóa thành lớp ProductRepository
.
Ngoài ra, chúng ta cũng định nghĩa một đối tượng ảo ProductManagementInterface
và truyền đối tượng ProductRepositoryInterface
vào đó như một tham số.
5. Cuối cùng, chúng ta sẽ sử dụng Service Contract đã định nghĩa ở trên trong một controller Magento 2, chẳng hạn như sau:
<?php
namespace Vendor\Module\Controller\Product;
use Magento\Framework\App\Action\Action;
use Magento\Framework\App\Action\Context;
use Vendor\Module\Api\ProductRepositoryInterface;
use Vendor\Module\Api\ProductManagementInterface;
class Index extends Action
{
/**
* @var ProductManagementInterface
*/
private $productManagement;
public function __construct(
Context $context,
ProductManagementInterface $productManagement
) {
parent::__construct($context);
$this->productManagement = $productManagement;
}
public function execute()
{
$productId = $this->getRequest()->getParam('id');
$this->productManagement->getProductById($productId);
// do other stuff with $product
}
}
Kết Bài
Với Dependency Inversion & Service Contract , Magento cho phép chúng ta tách biệt các phần khác nhau của mã nguồn và giảm thiểu sự phụ thuộc giữa chúng.
Điều này giúp cho việc mở rộng và bảo trì mã nguồn trở nên dễ dàng hơn và giảm thiểu các lỗi không mong muốn.
Cảm ơn các bạn đã dành thời gian đọc bài viết này. Có thắc mắc gì vui lòng comment phía dưới bài viết góp ý để mình cùng nhau phát triển nhé! Thân ái!