In 2012( both AX 2012 and the year 2012) something called Extension framework was introduced. It’s little known and sparsely used.
There were some performance limitations so even the few who knew it, did not prefer using it. In D365 as part of the drive to move away from over layering, this pattern/framework is being extensively used.
I came across it in the spring release code base. I would like to first introduce you to it and then explain the changes. If you are already familiar with it. You can skip the first part.
Now, let’s get back to the topic. This is more of a semi-framework / semi-code pattern. It was introduced to remove the necessity of overriding base types in order to extend the child types. Let me be more detailed.
Of all the examples I came across this was the simplest… So to save time I copied it below. The original post is available here.
I have a base enum defining the different Dynamics products:
Next, I have a base class named Dynamics.
It has a new method that's made private (forcing people to use the static constructor) and a run method that has an exciting infolog message.
private void new() { } public void Run() { info("base class"); }
Next, we'll make a derived class for each of the Dynamics products:
Each one will extend the base class and override the Run method to show a different infolog. Below is the code for the DynamicsAX and DynamicsCRM class, you get the idea for the other classes...
public class DynamicsAX extends Dynamics { } public void Run() { super(); info("Dynamics AX"); }
public class DynamicsCRM extends Dynamics { } public void Run() { super(); info("Dynamics CRM"); }
Great. So now that we have our sub types, we can create the "classic" construct method on our base class. On the base class "Dynamics", create a new static construct method, which takes our base enum as a parameter, and returns the correct sub type for it.
public static Dynamics construct(DynamicsProduct _product) { Dynamics dynamics = null; switch (_product) { case DynamicsProduct::AX: dynamics = new DynamicsAX(); break; case DynamicsProduct::CRM: dynamics = new DynamicsCRM(); break; case DynamicsProduct::GP: dynamics = new DynamicsGP(); break; case DynamicsProduct::NAV: dynamics = new DynamicsNAV(); break; case DynamicsProduct::SL: dynamics = new DynamicsSL(); break; default: throw error("Unsupported product"); } return dynamics; }
Nothing too crazy here, a very traditional pattern you see a lot in Dynamics AX. Finally, we can create a static main method on the base class to run it. For the sake of this article, we'll just hard code the product to be AX (because that's what we're really here for, isn't?). Feel free to create menu items with parmEnum or something, but to keep it short and to the point, hard coding will do. So again, on base class "Dynamics", add the main method:
public static void main(Args _args) { Dynamics dynamics = Dynamics::construct(DynamicsProduct::AX); dynamics.Run(); }
And of course, when we run this, we'll see the infologs showing. The base class declaring it's the base, and the sub type declaring it's Dynamics AX. Perfect! At this point, let's take a break and step back. What's wrong with this pattern? First of all, there is no way to extend this code base without either changing the code or overlayering (the construct method). Secondly, from a more design approach, it seems bad that the base class has to have explicit knowledge of all the sub classes. Put together, this is not very extension-friendly. If we would have two ISV products that both extend this, someone will have to merge the code. And nobody likes merging code. So, extension framework! The idea is to use the new attributes feature in the X++ language to decorate the classes, and make a constructor that can dynamically find the right sub class, assuming there is one. So first, we need to create a new attribute. An attribute is basically a class that extends the base SysAttribute class. An attribute can have different properties, some mandatory, some optional. In this case, we want to use the attribute on a class to tie it back to one of the enum values. So, our attribute should have a property to hold that enum value. The base enum was called "DynamicsProduct" so we'll declare a member variable to hold that. We'll call our attribute class "DynamicsProductAttribute".
class DynamicsProductAttribute extends SysAttribute { DynamicsProduct product; }
Since we don't really have class properties in X++, we need to create a set/get method, or as X++ likes to call it, a PARM method. Basically, a way to access the member variable to read or update it.
public DynamicsProduct parmDynamicsProduct(DynamicsProduct _product = product) { product = _product; return product; }
Now, to make this a mandatory property on the attribute we can add it as a method parameter on the new method. So, override the new method on the "DynamicsProductAttribute" class.
public void new(DynamicsProduct _dynamicsProduct) { super(); this.parmDynamicsProduct(_dynamicsProduct); }
So, that's really it for the attribute class. Now we can actually start using it to decorate our sub classes. No need to touch the base class, but on all the sub classes, we want to add the attribute in the classdeclaration. Again, I'll just show the example for the DynamicsAX and Dynamics CRM classes:
[DynamicsProductAttribute(DynamicsProduct::AX)] public class DynamicsAX extends Dynamics { }
[DynamicsProductAttribute(DynamicsProduct::CRM)] public class DynamicsCRM extends Dynamics { }
Decorate each sub-class with the attribute and the correct enum value. This approach makes a whole lot more sense. Now each sub-class is declaratively tying back to a specific enum value. So finally, we of course need to change the constructor on the base class to make use of this at run time. This is really the only part where there's a "framework" really in place. On the base class "Dynamics", we're going to change the static construct method. Now, we can ask the extension framework to grab us the class with a specific attribute. Of course we're interested in the DynamicsProductAttribute, but more importantly we're interested in the class that has that attribute but also has a specific value for its property (in our case, the enum DynamicsProduct). The extension framework just asks us what base class we want to grab sub classes for, as well as an INSTANCE of the attribute it should have. The reason we need an instance is because it needs all the properties (in our case we only have 1, but you could have several properties). To get all this done, we can call the static "getClassFromSysAttribute" method on the "SysExtensionAppClassFactory" class. We give it the base class name ("Dynamics") and the attribute we want, which has to be an instance, so create an instance and set the correct property value. Your new and improved (and shortened) construct method should look like this now:
public static Dynamics construct(DynamicsProduct _product) { Dynamics dynamics = SysExtensionAppClassFactory::getClassFromSysAttribute(classStr(Dynamics), new DynamicsProductAttribute(_product)); return dynamics; }
Since our static main was already using construct, you can just re-run the main and everything should work. Note that this class factory business caches EVERYTHING. If you've run it once, and then re-run after making changes, it may not pick up new classes or changed attributes. From the Tools menu, you can run Tools / Caches / Refresh Elements to flush the cache so it will pick up your changes.
Keeping the concepts same, this has been modified slightly to make it more easier for consumption. The old way still works but you couldn’t send arguments to initialize.
We basically needed only one thing here, an attribute to identify the correct child class. This needed extending SysAttribute, now in addition to that
we will have to implement SysExtensionIAttribute.
Implement methods parmCacheKey and useSingleton
Use SysExtensionGenericInstantiation to instantiate the members of class
Use getClassFromSysAttributeWithInstantiationStrategy instead of getClassFromSysAttribute
Now let’s look at an example :
The attribute class now looks like this –
class PurchRFQTypeFactoryAttribute extends SysAttribute implements SysExtensionIAttribute
PurchRFQType purchRFQType;
public void new(PurchRFQType _purchRFQType)
purchRFQType = _purchRFQType;
/// Returns the key used for storing cached data for this attribute.
public str parmCacheKey()
return classStr(PurchRFQTypeFactoryAttribute)+';'+int2str(enum2int(purchRFQType));
/// Determines if the same instance should be returned by the extension framework for a given extension.
/// When returning false, the SysExtension framework will create a new class instance for every invocation.
/// If the class is immutable, consider returning true to save memory and gain performance.
public boolean useSingleton()
Here enumeration of type PurchRFQType will determine what the child class will be. So, the child classes will be decorated like this:
[PurchRFQTypeFactoryAttribute(PurchRFQType::PurchReq)]
class PurchRFQCaseLineType_PurchReq extends PurchRFQCaseLineType
[PurchRFQTypeFactoryAttribute(PurchRFQType::Purch)]
class PurchRFQCaseLineType_Purch extends PurchRFQCaseLineType
The construct in the base class is already refactored. And it looks like this:
static PurchRFQCaseLineType construct(PurchRFQCaseLine _purchRFQCaseLine, PurchRFQCaseTable _purchRFQCaseTable = _purchRFQCaseLine.purchRFQCaseTable())
PurchRFQTypeFactoryAttribute attribute = new PurchRFQTypeFactoryAttribute(_purchRFQCaseLine.rfqType);
SysExtensionGenericInstantiation instantiation = new SysExtensionGenericInstantiation(_purchRFQCaseLine, _purchRFQCaseTable);
PurchRFQCaseLineType instance = SysExtensionAppClassFactory::getClassFromSysAttributeWithInstantiationStrategy(classStr(PurchRFQCaseLineType), attribute, instantiation) as PurchRFQCaseLineType;
The instantiation should match the signature of new() in the base class. Here it has 2 arguments.
protected void new(PurchRFQCaseLine _purchRFQCaseLine, PurchRFQCaseTable _purchRFQCaseTable)
If interested please look at the code in SysExtensionGenericInstantiation. It supports only 5 arguments. So, if you have more than 5 mandatory arguments in base class new method. You are doomed. Just kidding. Microsoft will take care of it. They have the onus to change all the base classes J
Also you can look at getClassFromSysAttributeWithInstantiationStrategy to understand the search logic to find the correct class. (I don’t want to explain these make this email longer than its already is and we all need some homework, don’t we?)
Now, when I want to introduce a new type of RFQ case line, all I had to do was add a new element to enum extension and then declare a class like this:
[PurchRFQTypeFactoryAttribute(PurchRFQType::Subcontract)]
class ACMPbmPurchRFQCaseLineType_Subcontract extends PurchRFQCaseLineType