Usando o SysExtension Framework para descobrir qual subclasse instanciar no Dynamics AX 2012
Publicado: 16 de fevereiro de 2025 às 00:25:55 UTC
Este artigo descreve como usar a estrutura SysExtension pouco conhecida no Dynamics AX 2012 e no Dynamics 365 for Operations para instanciar subclasses com base em decorações de atributos, permitindo um design facilmente extensível de uma hierarquia de classes de processamento.
Using the SysExtension Framework to Find Out Which Subclass to Instantiate in Dynamics AX 2012
As informações neste post são baseadas no Dynamics AX 2012 R3. Podem ou não ser válidas para outras versões. (Atualização: Posso confirmar que as informações neste artigo também são válidas para o Dynamics 365 for Operations)
Ao implementar classes de processamento no Dynamics AX, você frequentemente se depara com a criação de uma hierarquia de classes na qual cada subclasse corresponde a um valor enum ou tem algum outro acoplamento de dados. Um design clássico é então ter um método de construção na superclasse, que tem um switch que determina qual classe instanciar com base na entrada.
Isso funciona bem em princípio, mas se você tiver muitas entradas possíveis diferentes (muitos elementos em uma enumeração ou talvez a entrada seja uma combinação de vários valores diferentes), pode se tornar tedioso e propenso a erros de manutenção, e o design sempre tem a desvantagem de que você precisará modificar o método de construção se adicionar uma nova subclasse ou fizer alterações em qual subclasse deve ser usada com base em qual entrada.
Felizmente, há uma maneira muito mais elegante, mas infelizmente também muito menos conhecida, de fazer isso, ou seja, usando a estrutura SysExtension.
Este framework tira vantagem de atributos que você pode usar para decorar suas subclasses para fazer com que o sistema consiga descobrir qual subclasse deve ser usada para manipular o quê. Você ainda precisará de um método de construção, mas se feito corretamente, você nunca terá que modificá-lo ao adicionar novas subclasses.
Vamos dar uma olhada em um exemplo imaginário e dizer que você vai implementar uma hierarquia que faz algum tipo de processamento com base na tabela InventTrans. Qual processamento fazer depende do StatusReceipt e StatusIssue dos registros, bem como se os registros estão relacionados a SalesLine, PurchLine ou nenhum dos dois. Agora, você já está olhando para muitas combinações diferentes.
Digamos então que você sabe que por enquanto só precisa lidar com algumas combinações, mas também sabe que será solicitado que você consiga lidar com mais e mais combinações ao longo do tempo.
Vamos manter as coisas relativamente simples e dizer que, por enquanto, você só precisa manipular registros relacionados a SalesLine com um StatusIssue de ReservPhysical ou ReservOrdered. Todas as outras combinações podem ser ignoradas por enquanto, mas, como você sabe que precisará manipulá-las mais tarde, será necessário projetar seu código de uma forma que o torne facilmente extensível.
Sua hierarquia pode ficar parecida com esta por enquanto:
- MeuProcessador
- MeuProcessador_Vendas
- MyProcessor_Sales_ReservOrdered
- MyProcessor_Sales_ReservPhysical
- MeuProcessador_Vendas
Agora, você poderia facilmente implementar um método na superclasse que instancia uma subclasse com base em um ModuleInventPurchSales e um enum StatusIssue. Mas você precisará modificar a superclasse toda vez que adicionar uma subclasse, e essa não é realmente a ideia de herança na programação orientada a objetos. Afinal, você não precisa modificar RunBaseBatch ou SysOperationServiceBase toda vez que adicionar um novo trabalho em lote.
Em vez disso, você pode usar o framework SysExtension. Isso exigirá que você adicione outra classe, que precisa estender SysAttribute. Essa classe será usada como o atributo com o qual você pode decorar suas classes de processamento.
Esta classe é muito semelhante a como você criaria uma classe de contrato de dados para uma implementação SysOperation, pois ela terá alguns membros de dados e métodos de parâmetro para obter e definir esses valores.
No nosso caso, a ClassDeclaration pode ser parecida com isto:
{
ModuleInventPurchSales module;
StatusIssue statusIssue;
StatusReceipt statusReceipt
}
Você precisa criar um método new() para instanciar todos os membros de dados. Se desejar, você pode dar a alguns ou a todos eles valores padrões, mas eu não fiz isso.
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
;
super();
module = _module;
statusIssue = _statusIssue;
statusReceipt = _statusReceipt;
}
E você também deve implementar um método parm para cada membro de dados, mas eu omiti esses aqui porque tenho certeza de que você sabe como fazer isso - caso contrário, vamos considerar isso um exercício ;-)
Agora você pode usar sua classe de atributo para decorar cada uma de suas classes de processamento. Por exemplo, as declarações de classe poderiam se parecer com isto:
StatusIssue::None,
StatusReceipt::None)]
class MyProcessor_Sales extends MyProcessor
{
}
[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
StatusIssue::ReservOrdered,
StatusReceipt::None)]
class MyProcessor_Sales_ReservOrdered extends MyProcessor_Sales
{
}
[MyProcessorSystemAttribute(ModuleInventPurchSales::Sales,
StatusIssue::ReservPhysical,
StatusReceipt::None)]
class MyProcessor_Sales_ReservPhysical extends MyProcessor_Sales
{
}
Claro que você pode nomear suas classes da maneira que quiser, o importante aqui é decorar suas classes com atributos que correspondam ao tipo de processamento que elas fazem. (Mas tenha em mente que há convenções de nomenclatura para hierarquias de classes no Dynamics AX e é sempre uma boa ideia segui-las, se possível).
Agora que você decorou suas classes para identificar que tipo de processamento cada uma delas faz, você pode aproveitar a estrutura SysExtension para instanciar objetos das subclasses conforme necessário.
Na sua superclasse (MyProcessor), você pode adicionar um método de construção como este:
StatusIssue _statusIssue,
StatusReceipt _statusReceipt)
{
MyProcessor ret;
MyProcessorSystemAttribute attribute;
;
attribute = new MyProcessorSystemAttribute( _module,
_statusIssue,
_statusReceipt);
ret = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(MyProcessor), attribute);
if (!ret)
{
// no class found
// here you could throw an error, instantiate a default
// processor instead, or just do nothing, up to you
}
return ret;
}
A parte realmente interessante - e realmente o objeto (com o perdão do trocadilho) de todo esse post - é o método getClassFromSysAttribute() na classe SysExtensionAppClassFactory. O que esse método faz é aceitar o nome da superclasse de uma hierarquia (e essa superclasse não precisa estar no topo da hierarquia; significa simplesmente que apenas classes que estendem essa classe serão elegíveis) e um objeto de atributo.
Em seguida, ele retorna um objeto de uma classe que estende a superclasse especificada e é decorada com um atributo correspondente.
Obviamente, você pode adicionar mais validação ou lógica ao método construct, conforme desejar, mas o importante aqui é que, uma vez implementado, você nunca mais terá que modificar esse método. Você pode adicionar subclasses à hierarquia e, desde que certifique-se de decorá-las adequadamente, o método construct as encontrará, mesmo que elas não existissem quando ele foi escrito.
E quanto ao desempenho? Sinceramente, não tentei fazer um benchmark, mas meu pressentimento é que isso provavelmente tem um desempenho pior do que o design clássico de declaração switch. No entanto, considerando que de longe a maioria dos problemas de desempenho no Dynamics AX são causados pelo acesso ao banco de dados, eu não me preocuparia muito com isso.
Claro, se você estiver implementando algo que exigirá que milhares de objetos sejam criados rapidamente, você pode querer investigar mais, mas nos casos clássicos em que você apenas instancia um único objeto para fazer algum processamento demorado, duvido que isso importe. Além disso, considerando minha dica de solução de problemas (próximo parágrafo), parece que a estrutura SysExtension depende de cache, então em um sistema em execução duvido que tenha um impacto significativo no desempenho. Solução de problemas: Se o método de construção não encontrar suas subclasses, mesmo que você tenha certeza de que elas estão decoradas corretamente, pode ser um problema de cache. Tente limpar os caches no cliente e no servidor. Não deve ser necessário reiniciar o AOS, mas pode ser o último recurso.