Pega Patterns - Dynamic Class Referencing
I am a sucker for good patterns. Ones that save you from lots of busy work when you are developing large systems. Ones that integrate themselves nicely with the rest of your application and become “just the way we do things” quickly and without thought. Dynamic Class Referencing (known by its friends as DCR) is exactly that.
Defined back in the olden days of PRPC 5.4 by Pega itself, DCR provides a way to gain the benefits of compile-time type safety checks while injecting application-specific implementation class names at run time. Put simply: it allows you to follow Pega’s standard rule of “build your application in the Framework layer and only override the bare minimum required in the Implementation layer”.
While Pega defined the overall intent and a baseline implementation scheme for the pattern, it remains unknown to many, misunderstood by some and, even when implemented, not done so extensibly enough. Here, we’ll discuss how to build an application around DCR Done Right.
Classes and Metaclasses
DCR starts with a class. No, Pega doesn’t supply a base class for your implementation to extend (they publish the baseline pattern details, but no actual implementation from which to go). Instead, you need to BYOC (bring your own class).
To name this class, look at what DCR is supposed to be: a dynamic binding of entity types to the class names that implement them. For instance, if your application deals with Customers, you may have a Customer entity. That is represented in your system by the class MyCo-Data-Party-Customer. So, at its core, DCR is a lookup library that maps logical entity types to the class names that represent them in your app. Given that, a good name for your root DCR class might be MyCo-Data-ClassRef.
This class will be nothing but your library, and as such, is not something your app will necessarily manipulate – like it would your Customer class. Instead, it will feed in the appropriate data to let your app go about its business – and if you’re building a framework, for the apps built on top of your code to do THEIR thing. We’ve defined a metadata class.
This metadata class will gain children as you build out the various layers of your app following the standard Pega Enterprise Class Structure. When you build your Implementation layer, you’d create MyCo-Div-App-Data-ClassRef which has directed inheritance to MyCo-Data-ClassRef.
What would a Class be without Properties to set? Well, it’d be a title with no book. So how do we fill the pages? We create Properties that represent the entity types for which we need to map class names.
When defining the Properties, there are few different schools of thought:
- Create a Value Group where the key is the entity type and the value is the corresponding class name
- Create distinct Properties for each of the entity types that need mappings created
The benefit of the first is that you don’t have myriad properties that are created – with new ones being introduced every time you create a new class. The drawback of this method, however, is actually far scarier than creating a comparatively inconsequential number of rules: by creating a value list, all of your keys are simply strings that have to be typed manually every time you need to use them in all of your Activities, Data Transforms, Whens, etc. If you mis-type them anywhere, you won’t know until you get to run-time whether any individual rule is going to be buggy – and only then if you execute every code path.
While creating individual Properties for each class may seem heavy-handed, it limits the opportunities to mistype a value to just the Data Transform in which the class name is set. All other places where that value is used in your code references an existing Property rule and is compile-time checked to ensure validity: if you mis-spell the Property name, the system will let you know as you save the rule rather than letting a mistake creep up on you in Production.
Create individual Property rules for each class you need to map. It will save you debugging time down the road.
In addition to each of the class mapping Properties, you should also create baseline Properties for each of the various class namespaces in your app (typically Work-, Data- and Embed- derived classes). This further minimizes the amount of free-form string typing you need to do within your Data Transform rules and keeps your code as error-free as possible via compile time checks. Typically, I’ll create the following Properties to serve in this capacity:
Now that you have places in which to put data, go ahead and populate them. Create your Data Transform rules starting with the root DCR class for your app: MyCo-Data-ClassRef. In general, follow Pega’s pattern of using pyDefault as the name of your initialization Data Transform. Then start setting your properties with reckless abandon:
As you can see, by creating your DataBaseClass, you can then reuse that value to set any further Data- derived classes. The fewer times you have to type a free-form string, the fewer chances you will introduce a typo. If, however, you mistype .DataBaseClass, you’ll be told about it as soon as you try to save the rule in the form of a big error message.
One key piece of this whole puzzle is that the Call superclass data transform checkbox is ticked for each and every one of the pyDefaults you create in your chain of DCR classes. This will allow for the initial sets to happen in the MyCo-Data-ClassRef version, overlays for classes you have subclassed or added in your Framework layer to then be applied, and finally, your Implementation layer specifics to sit on top of the pile.
There are some special tips and tricks that apply to Component and Framework classes, but first we need to talk about how DCR gets used...
Data Page/Declared Page
Alright, now you want to get this data onto the Clipboard and available for use. Back in the olden days of 5.3 – 6.3, you’d have used a Declared Page for this. In the New World of 7.1 – 7.4, Data Pages are the vehicle. If you’ve gotten this far in this article, you’re well aware that Data Pages are just rebranded Declared Pages with some REALLY nice new functionality added. So why am I telling you about the differences? Well, it’s because of one of those new niceties that Data Pages provide that Declared Pages couldn’t handle: polymorphism.
Declared Pages could only be instantiated with the exact data type they were defined to be. Given that, you need a Declared Page in EACH layer of your application to correctly define the DCR class that will be instantiated lest the system throw exceptions at you because of a class “mismatch”.
Data Pages, on the other hand, are very well behaved object-oriented citizens. They need only be defined on the appropriate ancestor class and can be instantiated at run time with that class or any subclass available. If you needed any more fuel to add to your Pega 7 upgrade fire, there’s another stick.
Given that Pega 7 has been in the wild for a while now, Data Pages will be the focus of the rest of this discussion.
Create a new Data Page instance with a meaningful name: D_ClassLibrary or D_ClassRef or D_ClassLookup are all reasonable. D_EnjoyTheIceCream should be avoided. This should be a Page item with your MyCo-Data-ClassRef type that exists at the Thread scope.
There is an argument to be made that it could also be a Requestor Page, but that also limits your ability to allow the system to apply application changes on a per-Thread basis and, thereby, have potential new subclasses take effect as early as possible. Thread level provides the greatest degree of flexibility and, given how light-weight this Page is, doesn’t create an onerous memory impact.
The Data Page should be loaded by a Data Transform, but do not directly call your pyDefault just yet. Instead, create a different Data Transform to manage the process: LoadDCRLibrary. Why a middleman? This goes back to the polymorphism that Data Pages give us: we need something to inject the right DCR Library subclass name into the mix before we just start going and loading the page with the enterprise-level library (which will most assuredly not work right for your app at run time).
What is going on in that When condition there? This is where things get a little more exotic that you’ve likely seen in most Pega applications. You need to get the right DCR library class name for THIS application from somewhere in the system, but where? There are several options:
- Dynamic System Setting – Yes, you could do this, but if you have multiple application running on the same database (i.e., instance of Pega), you have no way to differentiate them: a D-A-S-S instance is a singleton in the database, and therefore, can contain only one value. Worse, you can not test your Framework and Implementation layers independently as you go about your build process.
- Rule System Setting – Another possibility, to be sure. And it solves the problem of only one version per database: you can tie these to individual RuleSets and Versions! But what if you have a pair of applications that run on the same RuleSet stack, but need slightly different handling for certain classes? An esoteric problem, absolutely, but conceivable.
- A Custom Class and Database Table – This will address the problems above, but is fairly heavy-handed. Especially when you have…
- Custom Fields on the Application Rule – This is what you’ll want to use.
Every single Pega record (Rule-, Data-, you name it) allows for the inclusion of Custom Fields on their History tabs. These values are tied to that instance itself, indexed out into a database table for querying and otherwise, just highly extensible key/value pair metadata that you can use. Since they’re tied to each individual application rule, you can test any application that runs on your Pega instance – including the individual Framework and Implementation layer components within applications. Since they’re tied to the individual versions of an Application rule, you can change them as you build out your app. Since they’re indexed out, they’re already in the database and query-able without needing a new class and table – and better even than that – as you save your app rule (or migrate it from environment to environment), they automatically come along for the ride, so you don’t lose values through a simple save-as process.
With all of that background, look a little closer at the when:
@IsInPageList(“DCRLibraryClassName”, “.pyCFieldName”, Application.pyCustomFields)
This is using the omni-present Application Clipboard page and looking in the pyCustomFields Page List for an instance where the pyCFieldName is DCRLibraryClassName. Simple enough. If it doesn’t find it, the system will default to the baseline DCR Class name. If it does, however, it performs the set:
You then set the Data Page’s pxObjClass value to this class name and completely ignore Pega’s warning that you just set pxObjClass. While some rules are good citizens in a DCR world (Activities), Data Transforms still lag behind in that they do not provide a way to create a Page with a dynamic type. You should either get used to this warning or possibly remove it altogether (to keep it from littering your Preflight reports).
Finally, the system finishes by executing the pyDefault Data Transform.
Data Transforms: Part Deux
Do you remember when I teased you earlier about how to handle Frameworks and Components in the DCR world? Well, your long wait is now over because here we go. Rather than getting their own DCR classes, the get their own Data Transforms that get called by either the Enterprise or the Implementation layer as appropriate. Why, you ask? Well, Pega espouses a dual inheritance model: Pattern followed by Directed. For an Implementation layer built on a single framework and a single Enterprise, that would probably be okay.
Since 7.2, however, Pega has allowed for multiple built-on applications. What happens if your Implementation layer includes 2 or more built-on Frameworks, each supplying their own DCR class? Frameworks shouldn't really build on each other (unless they're logical outgrowths of one another, but that's another story). They should instead be standalone and portable.
So instead, they supply a self-named Data Transform: MyCoolFW, for instance. Then, another entry is made on the App Rule's Custom Fields section for the components upon which it's built.
The DCR Library load mechanism then spins over each of the DCRComponentDataTransformNames in your comma-separated list and applies them sequentially.
This is where the rubber really meets the road: you have the infrastructure in place to support DCR in your app and you want – no, need – to start using it. What are the rules then?
- Use DCR everywhere you instantiate a Page in an Activity via Page-New.
- Use DCR everywhere you change a Page’s class in an Activity via Page-Change-Class.
- Use DCR in any Data Transform where you create a new Page or append items to a List or Group. This requires you set the .pxObjClass Property on those Pages and will generate warnings unless you stop Pega from generating them.
- Use DCR in any Obj-* method call in an Activity.
- Use DCR in any RDB-* method call in an Activity.
- Use DCR in any code that checks whether a Page is of a particular type. The corollary to this is to use the out-of-the-box utility function isOrInheritsFrom in the Utilities library to ensure that you get the benefit of your polymorphic class structure. Rarely, if ever, should you do a direct check of .pxObjClass == “Some Class Name”.
This is pretty far-reaching. If you’re not using DCR presently, it may seem pretty daunting to bring it into your system. The good news is that it doesn’t have to be done all-at-once; it can be phased in. Further, there are exceptions to the above rules:
- DCR does not apply to out-of-the-box Pega classes that you will never (or rarely) subclass (Code-*, System-*, Rule-*, Data-Admin-Operator-ID, etc.). Those can be hard-coded without any particular reservation. If Pega decides to change those classes, there are likely bigger changes required to your app than worrying about DCR.
- Integration classes do not get DCR’d. Anything MyCo-Int- derived is special if you follow Pega’s standard practices for allowing integrations to be versioned. When you consume an external service via the wizard, you provide Pega with a base class into which to generate subclasses, Properties, Activities, etc. This would look something like: MyCo-Int-AService-v1-. The next time you consume the service, you would do so with an incremented version class: MyCo-Int-AService-v2-. This allows for multiple versions of the service to exist in your system without breaking previous code. The trick is that Pega doesn’t give you the ability to identify inheritance chains through the wizard, so all subclasses that are generated just pattern and direct inherit from the root class you provide. Therefore, integration classes can’t polymorph (this is by design and not a drawback of the way Pega does things – if you change data types for a field in the integration, you can’t have it inheriting from another class that previously defined the field).
As a real-world example, let’s take an Activity rule that you’re writing that needs to append new items to a Page list using the KeyValuePair class that was defined in our DCR library. You would create your appropriate context and looping step and, in your Page-New, pass the DCR reference to the KeyValuePair class instead of a hard-coded class name.
If you built this rule in either your Enterprise or your Framework layers, by using a DCR reference, you wouldn’t need to override this rule in your Implementation layer if you created a subclass of MyCo-Data-KeyValuePair. Instead, you would just supply the appropriate class name in your Implementation layer’s pyDefault for its DCR class and the code would just magically work, creating the right object type at run time.
What’s just as valuable is – just like if you had hard-coded a class name – Pega can tell you if you mis-typed your DCR reference because the property won't exist!
Updating the Library
The concern that is most often raised about DCR is the cost of maintaining your class library. Each time you create a new class in your system, you have to add a new entry to the appropriate pyDefault Data Transform. In the early going of an application’s life, this can introduce a lot of updates to those rules. Once your application is mature, however, the library will only grow by a handful of classes here and there. Further, the cost of creating a Property and adding a row to a Data Transform is immaterial when compared to the amount of code that will not be duplicated across layers in your system by injecting the right class bindings at the right time.
Patterns like DCR give your applications the flexibility to grow in directions you hadn’t originally conceived. Components can be multi-purposed, rudimentary object types can be re-used across the Enterprise and given appropriate context-specific definitions where appropriate. The biggest hurdle is getting started.
Colin Campbell and Ryan Mongold are the owners of Stratosphere Technical Consulting, specializing in the strategic development of large-scale Pega Applications, Programs, and Centers of Excellence using a proprietary model for the ongoing evolution of business and technical BPM maturity across the enterprise.
Want to learn more about what we can do for your organization? Contact us at email@example.com.
Interested in working with a company rather than for one? We’re always looking for the best talent in the industry. Contact us at firstname.lastname@example.org.