The dangers of delayed compilation
TL; DR;
I’ve previously mentioned a handy way to allow moving components between packages during upgrades even up the package dependency hierarchy. It came with a caveat about Custom Labels and more importantly about LWCs components. This article is cautionary tale no. 2. Maybe it’s not such a good idea after all.
The Trick
Temporarily remove a metadata artefact from its package via Setup > Installed Packages > View Components > remove. You are only dissociating it from the package without actually deleting it from the org, allowing the next upgrade of the refactored (parent) package to assume ownership of the metadata artefact again. And it even works with Unlocked Packages with Namespace.
While this hack really is a lifesaver in some cases, you should be more than careful with it. You can cause some major trouble this way that (depending on your organisational rules) may be quite challenging to identify and fix.
The Story
This time we will look more closely at those namespaced packages. Specifically classes inside them. The main benefit of a namespace when it comes to classes is the true boundary it provides. Nothing can reach inside the package, unless you intentionally expose classes and methods as either global
or @NamespaceAccessible
.
No-namespace packages expose all public code. Try applying some advanced software patterns without exposing something public that is not meant to be called outside of your package. Can’t be done.
@NamespaceAccessible annotation is super helpful. It’ll make the public code available to other classes in packages in the same namespace. You can split your projects into multiple packages (let’s not waste time here to discuss why that’s a good idea) allowing some cross package integration without exposing a lot of code as global
.
This, however, is where our component moving trick becomes very dangerous indeed. When you remove a class from its package in the subscriber org you also effectively remove it from the namespace. It does not look like you do (the class still has a namespace listed on its detail page) but that is what happens. And it means that whatever code you had annotated as @NamespaceAccessible
isn’t. Until you upgrade your packages and the class again gets assumed back into a package and into the namespace.
Nothing but a bit of downtime during our release, that’s acceptable, right? in most cases it is. But it is dangerous. Everyone must know the potential impact for instance if you have continuous activity in the org (e.g. integrations running). Making changes manually takes time, people might be tempted to pre-do manual release steps to save some time during out-of-ours release windows. Or worse, mistakenly apply them in the wrong org!
Any manual release steps are risky but this one in particular can cost you if not careful. And you might not even realise it right away. Apex classes are compiled on-demand (in most situations) and it seems that removing a class from its package via our workaround does not force it to recompile the next time it’s referenced like updating it directly would. This means it continues to work fine until something else triggers this compilation. For us this was approximately 24 hrs later! It’s not always that obvious at all to connect the error this causes to a release which happened “so long ago” and caused no issues.
The Lesson
So what’s a better way to move components between packages? The answer sounds obvious but takes experience to accept. It is: don’t! Just.. do not do it.
You need to refactor though, I know. You can still do it. Instead of moving your component or class, create a new one in the package where it needs to be. Chances are the original could use a better name anyways. And some cleanup too. If not.. tough! Think of a new name and go ahead. Then update the dependent packages to use this new interface.
The beauty of this approach is that nothing ever breaks and there are no manual steps needed. And you don’t even have to do the two things at the same time. It could be that separate teams work on the different packages. You can give them time to update all the references and release them in their own time. Only when you know no-one depends on the obsolete component anymore you remove it completely.
This is much safer. And more responsible too. And after all, it is what you would expect anywhere else to be the right approach. We tend to think coding for Salesforce is different, special. But it really isn’t.