Como evitar a violação da Lei de Deméter (“Objetos novos não devem conter uma referência de campo a um objeto injetável”)

7

Nas Regras de uso da injeção de dependência , os devdocs do Magento 2 afirmam:

Objetos renováveis ​​não devem conter uma referência de campo a um objeto injetável, nem devem solicitar uma em seu construtor. Esta é uma violação da Lei de Deméter .

Entendo que esse é um bom objetivo, mas como isso é realmente possível nos modelos Magento 2?

Se dermos uma olhada no módulo Customer, que foi apresentado como um exemplo brilhante para a nova arquitetura, a assinatura do construtor do modelo do cliente é semelhante a esta:

public function __construct(
    \Magento\Framework\Model\Context $context,
    \Magento\Framework\Registry $registry,
    \Magento\Store\Model\StoreManagerInterface $storeManager,
    \Magento\Eav\Model\Config $config,
    \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
    \Magento\Customer\Model\ResourceModel\Customer $resource,
    \Magento\Customer\Model\Config\Share $configShare,
    \Magento\Customer\Model\AddressFactory $addressFactory,
    \Magento\Customer\Model\ResourceModel\Address\CollectionFactory $addressesFactory,
    \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder,
    GroupRepositoryInterface $groupRepository,
    \Magento\Framework\Encryption\EncryptorInterface $encryptor,
    \Magento\Framework\Stdlib\DateTime $dateTime,
    CustomerInterfaceFactory $customerDataFactory,
    DataObjectProcessor $dataObjectProcessor,
    \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
    \Magento\Customer\Api\CustomerMetadataInterface $metadataService,
    \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry,
    \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
    array $data = []
)

E Magento\Framework\Model\Contextsozinho, que é usado por todos os modelos, leva cinco argumentos injetáveis .

Aparentemente modelos, embora newable , não são um bom exemplo a todos para esta regra.

Quero seguir as práticas recomendadas com minhas próprias classes (nem mesmo modelos), mas preciso acessar itens como repositórios de entidades relacionadas ou o gerenciador de eventos. Qual seria a maneira preferida de lidar com isso sem quebrar a regra de cima?

Atualmente, costumo ignorá-lo ou, na melhor das hipóteses, vê-lo como orientação amigável, não como regra.

Fabian Schmengler
fonte

Respostas:

2

Neste exemplo, acho que grande parte da lógica dos modelos deve ser extraída em classes de serviço externas, que executam código no modelo.

Dessa forma, ao carregar, por exemplo, o modelo através do repositório, a classe de repositório teria dependências nas novas classes de serviço, as quais dependem das classes que estão atualmente no construtor do modelo.

Penso que como desenvolvedores de extensões poderíamos tentar seguir as regras, mas ainda há muita refatoração a ser feita pelo magento.

=== editar ===

Encontramos algo novo. No módulo do cliente, o modelo é dividido em um modelo de dados e uma classe de serviço (o modelo original).

Para o DataInterface, agora o DataModel é injetado: https://github.com/magento/magento2/blob/develop/app/code/Magento/Customer/etc/di.xml#L17

O Modelo de Dados contém apenas os dados e nenhuma lógica comercial: https://github.com/magento/magento2/blob/develop/app/code/Magento/Customer/Model/Data/Customer.php

Portanto, agora o Modelo original pode ser visto como uma Classe de Serviço que contém a lógica de negócios, tornando totalmente aceitável que ele faça referência a outros injetáveis.

David Verholen
fonte
Boa descoberta. Mas o modelo original ainda não é injetável . Talvez este seja apenas um passo da refatoração? E não devemos confundir "sem dependências de serviços" com "sem lógica de negócios". A redução de modelos para objetos de dados simples está indo ao extremo e IMHO não é algo pelo qual se esforçar.
Fabian Schmengler
1
ok, mas é difícil incluir muita lógica de negócios sem injetar injetáveis ​​(isso parece confuso). Então eu acho que é um bom passo para dividi-los. O modelo original torna-se injetável assim que perde sua identidade, não é? Acho que descontinuar os métodos de salvar e carregar e extrair o modelo de dados foi o primeiro passo para fazer isso. O passo seguinte seria talvez se livrar do modelo completo "velho" e delegar as funcionalidades a mais adequados classes, menores
David Verholen
1

Estamos tentando afastar as pessoas do acesso direto ao código dentro de um módulo e, em vez disso, passando por uma API mais controlada e bem definida. O objetivo é permitir atualizações mais suaves entre os lançamentos - quanto mais pessoas usarem uma API definida, maiores serão as atualizações.

Um conceito importante nos arquivos di.xml é o elemento "preferência". Ele permite que o código faça referência a definições de interface (e, portanto, não esteja vinculado a uma implementação específica), mas quando você solicita uma instância, ele sabe qual classe usar.

Portanto, se você deseja uma instância CustomerInterface (que é uma classe que implementa a interface), solicita uma instância da interface (não procura a classe de implementação). Vamos procurar um elemento para você e usá-lo para identificar a classe de implementação. Dessa forma, outro módulo poderia substituir a preferência do arquivo di.xml e trocar por uma classe de implementação diferente (se houver algum bom motivo para fazê-lo).

Além disso, quando você cria objetos através do gerenciador de objetos dessa maneira, não precisa especificar os argumentos para a lista de construtores. O gerenciador de objetos novamente usa o arquivo di.xml para localizar todas as estruturas de dados necessárias e as fornece automaticamente ao construtor.

A diferença entre "classes de serviço" e "modelos de dados" em uma resposta separada é "classes de serviço" (que eu suponho que significa que as interfaces no diretório Api) fornecem métodos a serem chamados. Os "modelos de dados" (que eu suponho que significam as interfaces no diretório Api / Data) são estruturas de dados passadas para / de "classes de serviço". O objetivo é que alguém fora do módulo não precise saber qual classe está implementando as interfaces. Pode ser um modelo, um objeto de valor, não importa desde que você programe para as interfaces e nunca mencione nomes de classes diretamente (exceto no arquivo di.xml).

Alan Kent
fonte