<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/" xmlns:discourse="http://discourse.org/rss/modules/discourse/">
<channel>
<title>Ribau on Roque</title>
<link>https://j-roque.com/authors/ribau/</link>
<description>Recent content in Ribau on Roque</description>
<generator>Hugo -- gohugo.io</generator>
<language>en</language>
<managingEditor>joao.roque.94@gmail.com (João Roque)</managingEditor>
<webMaster>joao.roque.94@gmail.com (João Roque)</webMaster>
<copyright>© 2026 João Roque</copyright>
<lastBuildDate>Fri, 24 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://j-roque.com/authors/ribau/index.xml" rel="self" type="application/rss+xml"/>
<item>
<title>Understanding Dynamic Execution Extensions (DEEs): How They Really Work</title>
<link>https://j-roque.com/posts/20260724-howdodeeswork/</link>
<pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate>
<author>joao.roque.94@gmail.com (João Roque)</author>
<guid>https://j-roque.com/posts/20260724-howdodeeswork/</guid>
<description>Most engineers learn to create a DEE long before they understand the execution pipeline underneath it. This is what that pipeline actually looks like — and why it matters once a project gets big.</description>
<content:encoded>&lt;p&gt;&lt;img src="https://j-roque.com/posts/20260724-howdodeeswork/featured.png" alt="Understanding Dynamic Execution Extensions (DEEs): How They Really Work" /&gt;&lt;/p&gt;&lt;p&gt;Most developers learn how to create a &lt;strong&gt;Dynamic Execution Extension&lt;/strong&gt; (&lt;code&gt;DEE&lt;/code&gt;) long before they understand how the execution pipeline underneath it actually works. That gap doesn&amp;rsquo;t matter on a small project. It matters enormously once a project grows: it starts showing up as performance issues, duplicated logic, Action Groups picked by guesswork, and debugging sessions that end with someone staring at a stack trace error they&amp;rsquo;ve seen a dozen times but never actually understood.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Overview
&lt;div id="overview" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#overview" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;This blog post is about what happens behind the scenes: why DEEs exist, how the CM MES product code actually finds and triggers them, how to choose between &lt;code&gt;Pre&lt;/code&gt; and &lt;code&gt;Post&lt;/code&gt;, &lt;code&gt;Operation&lt;/code&gt; and &lt;code&gt;Orchestration&lt;/code&gt;, and the handful of best practices that separate a maintainable implementation from a production incident waiting to happen.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Why DEEs Exist in the First Place
&lt;div id="why-dees-exist-in-the-first-place" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-dees-exist-in-the-first-place" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;When Critical Manufacturing built its MES, the team made a deliberate choice not to ship a closed system — the kind where the only way to add logic is to copy the product source and modify it. That approach might work in the short term on a fully managed CM Project, but it&amp;rsquo;s not sustainable: you lose upgrades, you duplicate everything you touch, and you end up maintaining a fork of the product forever.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DEEs&lt;/code&gt; — Dynamic Execution Extensions — are one of the main answers to that problem. They give implementation teams, and power users, a way to extend and adapt the product without touching product code and without depending on Critical Manufacturing for every new requirement. A client with enough in-house knowledge can build their own logic and become genuinely independent. That&amp;rsquo;s the point of the mechanism, and it&amp;rsquo;s worth keeping in mind every time you&amp;rsquo;re deciding &lt;em&gt;how&lt;/em&gt; to use it — because a mechanism built for extensibility can just as easily be used to build something unmaintainable.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Where DEEs Fit in the Request Pipeline
&lt;div id="where-dees-fit-in-the-request-pipeline" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#where-dees-fit-in-the-request-pipeline" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Strip away the specifics and a standard MES method call is simple: a request comes in, it flows through the method, and a result comes out. &lt;code&gt;DEEs&lt;/code&gt; insert themselves at exactly two points around that flow. Before the method runs, the pipeline checks whether there&amp;rsquo;s anything registered to execute first, if so, runs it.&lt;/p&gt;
&lt;p&gt;The standard method flow executes.&lt;/p&gt;
&lt;p&gt;Then, after the method completes, the pipeline checks again for anything registered to run afterward, executes it, and only then returns the output.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the entire mental model.&lt;/p&gt;
&lt;p&gt;Everything else — naming conventions, Action Groups, Pre versus Post — is just detail on top of this one idea: a hook before, a hook after, and the standard method in between.&lt;/p&gt;
&lt;div class="not-prose"&gt;
&lt;img
src="https://image.j-roque.com/posts/20250821-howdodeeswork/MESRequest.png"
alt="MES Request"
loading="lazy"
decoding="async"
style="background-color:#fff; padding:1rem; border-radius:0.5rem;"
/&gt;
&lt;/div&gt;
&lt;br&gt;
&lt;div class="not-prose"&gt;
&lt;img
src="https://image.j-roque.com/posts/20250821-howdodeeswork/DEEsHook.png"
alt="DEE Hooks"
loading="lazy"
decoding="async"
style="background-color:#fff; padding:1rem; border-radius:0.5rem;"
/&gt;
&lt;/div&gt;
&lt;h2 class="relative group"&gt;Naming Your Action Group: The Convention Nobody Tells You About
&lt;div id="naming-your-action-group-the-convention-nobody-tells-you-about" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#naming-your-action-group-the-convention-nobody-tells-you-about" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The Action Group name isn&amp;rsquo;t &lt;strong&gt;arbitrary&lt;/strong&gt;, and it isn&amp;rsquo;t something you have to reverse-engineer from the product source. It follows a simple and fixed pattern and is described in the documentation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{NamespaceSuffix}.{ClassName}.{MethodName}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Take the &lt;code&gt;CreateMaterial&lt;/code&gt; method as an example. Its .NET namespace is &lt;code&gt;Cmf.Navigo.BusinessOrchestration.MaterialManagement&lt;/code&gt;, its class is &lt;code&gt;MaterialOrchestration&lt;/code&gt;, and the method is &lt;code&gt;CreateMaterial&lt;/code&gt;. Strip the namespace down to its suffix — &lt;code&gt;MaterialManagement&lt;/code&gt; — and combine it with the class and method names, and you get:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MaterialManagement.MaterialOrchestration.CreateMaterial&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Append &lt;code&gt;.Pre&lt;/code&gt; or &lt;code&gt;.Post&lt;/code&gt; depending on where you want your logic to run, and that&amp;rsquo;s your Action Group. It&amp;rsquo;s a mechanical process, not magic, it&amp;rsquo;s true because of retro-compatibility, a handful of older methods don&amp;rsquo;t follow this convention to the letter, but those are documented exceptions.&lt;/p&gt;
&lt;p&gt;You don&amp;rsquo;t have to know this by heart, there&amp;rsquo;s a reference for exactly this: the &lt;a
href="https://developer.criticalmanufacturing.com/reference/api-extensionpoints/index.html"
target="_blank"
&gt;Extension Points documentation&lt;/a&gt; lists every extension point available in MES. It&amp;rsquo;s a resource that, according to the training session feedback, far fewer developers know about it than they should.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Magic Behind the Curtain: StartMethod and EndMethod
&lt;div id="the-magic-behind-the-curtain-startmethod-and-endmethod" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-magic-behind-the-curtain-startmethod-and-endmethod" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Knowing the naming convention answers &lt;em&gt;what&lt;/em&gt; your Action Group is called. It doesn&amp;rsquo;t answer how the product code actually finds and executes your DEE. That happens inside two utility calls that wrap the standard method body:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-cs"&gt;namespace Cmf.Navigo.BusinessOrchestration.MaterialManagement
{
    internal partial class MaterialOrchestration : IMaterialOrchestration
    {
        public CreateMaterialOutput CreateMaterial(CreateMaterialInput createMaterialInput)
        {
            _utilities.StartMethod(objectTypeName, &amp;#34;CreateMaterial&amp;#34;,
                new KeyValuePair&amp;lt;String, Object&amp;gt;(nameof(CreateMaterialInput), createMaterialInput));
// Method code…
_utilities.EndMethod(createMaterialOutput.Material.EntityType.Id,
                createMaterialOutput.Material.Id,
                new KeyValuePair&amp;lt;String, Object&amp;gt;(&amp;#34;CreateMaterialInput&amp;#34;, createMaterialInput));
return createMaterialOutput;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;StartMethod&lt;/code&gt; builds the Action Group name and appends &lt;code&gt;.Pre&lt;/code&gt;; &lt;code&gt;EndMethod&lt;/code&gt; builds the same name and appends &lt;code&gt;.Post&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Each call checks whether any DEE is registered against that resolved name, and if so, executes it. That&amp;rsquo;s the whole mechanism, there&amp;rsquo;s no separate dispatcher, no background listener. It&amp;rsquo;s these two calls, wrapping the method body, doing the lookup and triggering the DEEs synchronously as part of the same call stack.&lt;/p&gt;
&lt;h2 class="relative group"&gt;When Multiple DEEs Share an Action Group
&lt;div id="when-multiple-dees-share-an-action-group" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#when-multiple-dees-share-an-action-group" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s nothing stopping you from registering as many DEEs as you want against the same Action Group, and in real implementations that can happen, a single &lt;code&gt;TrackOut.Post&lt;/code&gt;, for example, might have several DEEs attached to handle different pieces of logic.&lt;/p&gt;
&lt;p&gt;When that happens, execution runs in a &lt;strong&gt;cascading sequence, lowest order to highest&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That guarantee only holds if you actually set distinct orders. Leave two DEEs at the same order — which happens more often than you&amp;rsquo;d think, especially when master data gets bulk-loaded — and the execution order between them is undefined. You don&amp;rsquo;t get to choose which one runs first, and if one depends on state the other mutates, that&amp;rsquo;s a real bug waiting for the wrong day to show up.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a second, subtler trap: a DEE&amp;rsquo;s position is relative to the Action Group, not absolute. The same DEE that runs third in one Action Group can run first in a different one it&amp;rsquo;s also attached to. If that DEE assumes something was already done by &amp;ldquo;the DEEs before it,&amp;rdquo; that assumption silently breaks the moment it&amp;rsquo;s reused somewhere else.&lt;/p&gt;
&lt;p&gt;And then there&amp;rsquo;s the &lt;strong&gt;plain performance math&lt;/strong&gt;. If ten DEEs are attached to &lt;code&gt;TrackOut.Post&lt;/code&gt; and each takes a 300 milliseconds to run, you&amp;rsquo;ve added three seconds to every track-out — permanently, not as a one-off cost. On a line producing a unit every couple of seconds, adding seconds to track-out isn&amp;rsquo;t a rounding error, it&amp;rsquo;s a bottleneck that turns into a production down event.&lt;/p&gt;
&lt;p&gt;The reflex response, merge all ten DEEs&amp;rsquo; code into one, doesn&amp;rsquo;t help a lot. The code is the same code, running the same instructions; consolidating it into a single DEE buys you little in runtime and costs you readability and debuggability.&lt;/p&gt;
&lt;p&gt;Try and reduce chaining, either vertically (in the same action group) or horizontally (hooking both in the service and in operations called by the service). Be simple and obvious, smaller, single-purpose DEEs are easier to understand, easier to debug, and easier to reorder safely later.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="DEE Hierarchy"
src="https://image.j-roque.com/posts/20250821-howdodeeswork/DEEHierarchy.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class="relative group"&gt;Pre or Post? Fail Fast, Then Act
&lt;div id="pre-or-post-fail-fast-then-act" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#pre-or-post-fail-fast-then-act" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The decision between &lt;code&gt;Pre&lt;/code&gt; and &lt;code&gt;Post&lt;/code&gt; usually comes down to one question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Does this logic depend on the operation having already happened, or does it need to stop the operation from happening at all?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Take rework. If a DEE needs to send a material to a rework flow &lt;em&gt;after&lt;/em&gt; it&amp;rsquo;s been processed, it has to run on &lt;code&gt;Post&lt;/code&gt;. It wouldn&amp;rsquo;t make sense to send the material to rework before the track-out that triggers the rework in the first place.&lt;/p&gt;
&lt;p&gt;Now flip it: you want to block a track-out entirely if the material is missing a required attachment. That validation has to run on &lt;code&gt;Pre&lt;/code&gt;. You could technically validate after the track-out and roll everything back on failure, but that wastes a full operation&amp;rsquo;s worth of resources and time just to fail. The entire point of a validation is to &lt;strong&gt;fail fast&lt;/strong&gt;, before you&amp;rsquo;ve done any work you&amp;rsquo;ll have to undo.&lt;/p&gt;
&lt;p&gt;That generalizes cleanly: validations belong at &lt;code&gt;Pre&lt;/code&gt;. If you find yourself putting a process validation on &lt;code&gt;Post&lt;/code&gt;, that&amp;rsquo;s worth a second look.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;All the validations that you do should be at the pre of any Action Group.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 class="relative group"&gt;Operation or Orchestration? It depends&amp;hellip;
&lt;div id="operation-or-orchestration-it-depends" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#operation-or-orchestration-it-depends" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;This is the question that generates the most contention in practice.&lt;/p&gt;
&lt;p&gt;The mechanical difference is straightforward.&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;Operation&lt;/code&gt; — for example &lt;code&gt;BusinessObjects.MaterialCollection.TrackIn&lt;/code&gt; — does only the essential, object-level work, and it&amp;rsquo;s &lt;em&gt;guaranteed&lt;/em&gt; to run every time that operation executes, no matter what is the service calling it.&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;Orchestration&lt;/code&gt; — for example &lt;code&gt;MaterialManagement.MaterialManagementOrchestration.ComplexTrackInMaterials&lt;/code&gt; — wraps several operations together and triggers everything around them: data collections, checklists, and whatever else the UI action is supposed to kick off. Orchestrations are usually what UI buttons call through their underlying services; they are &lt;em&gt;not&lt;/em&gt; guaranteed to run if a lower-level operation is invoked directly, by a different orchestration, or by another DEE.&lt;/p&gt;
&lt;p&gt;The rule of thumb: If you want a system wide impact default to &lt;code&gt;Operation&lt;/code&gt;, if you want to pinpoint the change to particular API call or behavior choose the specific &lt;code&gt;Orchestrations&lt;/code&gt; you want to impact. Take not that when making system wide extensions, they will impact the whole system and as such you need to be extra carefully validating this. Also, it&amp;rsquo;s important to understand what is the granularity of context needed for your extensibility, that may also impact this decision.&lt;/p&gt;
&lt;p&gt;Where it gets genuinely contested is &lt;em&gt;where inside the orchestration lifecycle&lt;/em&gt; to hook in when orchestration-level context is actually needed. Hooking at end of the orchestration as the requirement allows — triggering on &lt;code&gt;Post&lt;/code&gt; at the orchestration level rather than reaching into an operation in the middle of it can ensuring that the standard way the API works will be kept as most as possible.&lt;/p&gt;
&lt;p&gt;The counterargument, is that hooking into an operation mid-orchestration and then manipulating an object there is a well-known way to hit &lt;code&gt;&amp;quot;the object has changed&amp;quot;&lt;/code&gt; errors, because the DEE mutates something that isn&amp;rsquo;t part of the input being passed back up the call chain, and the reference the orchestration expects gets silently invalidated. Debugging that is its own kind of forensic exercise: reconstructing which operation, inside which orchestration, touched what, in what order.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also a performance angle: calling an orchestration &lt;em&gt;from inside&lt;/em&gt; a DEE — instead of calling the underlying operation directly — adds the orchestration&amp;rsquo;s overhead every time that DEE runs. The trade-off cuts both ways: skip the orchestration and call the operation directly, and you might silently lose whatever the orchestration was responsible for triggering. A concrete example from the session — calling the &lt;code&gt;TrackIn&lt;/code&gt; operation directly, instead of going through the orchestration, means the data collections and checklist instances that the UI flow normally opens simply never get created.&lt;/p&gt;
&lt;p&gt;None of this resolves into a single rule. It depends on what you&amp;rsquo;re trying to guarantee, and what you&amp;rsquo;re willing to skip.&lt;/p&gt;
&lt;h2 class="relative group"&gt;The Documentation Trick: Trace What an Orchestration Actually Calls
&lt;div id="the-documentation-trick-trace-what-an-orchestration-actually-calls" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#the-documentation-trick-trace-what-an-orchestration-actually-calls" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The technique behind that worked example is worth calling out on its own, because it&amp;rsquo;s underused. The &lt;a
href="https://developer.criticalmanufacturing.com/reference/api-extensionpoints/index.html"
target="_blank"
&gt;Extension Points documentation&lt;/a&gt; doesn&amp;rsquo;t just list Action Group names — for orchestrations, it shows what they invoke underneath, recursively. Instead of reading product source to reconstruct a call graph by hand, you can search the orchestration the UI calls, see the Action Groups available on it, and follow the chain down through every operation and sub-orchestration it triggers.&lt;/p&gt;
&lt;p&gt;That lets you make an informed trade-off: the topmost orchestration usually does the most (and costs the most), and each layer down sheds functionality you may or may not need. Pick the shallowest layer that still gives you the context your DEE actually requires — nothing more.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Anatomy of a DEE: Test Condition, Action Code, and References
&lt;div id="anatomy-of-a-dee-test-condition-action-code-and-references" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#anatomy-of-a-dee-test-condition-action-code-and-references" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Every DEE Action has a &lt;strong&gt;Test Condition Code&lt;/strong&gt; block that must return a boolean, and an &lt;strong&gt;Action Code&lt;/strong&gt; block that runs if that condition is true.&lt;/p&gt;
&lt;p&gt;Inside the action code, the &lt;code&gt;UseReference&lt;/code&gt; directive takes two string arguments: the assembly to reference, and the namespace within it to use — the DEE equivalent of adding a reference in Visual Studio plus a &lt;code&gt;using&lt;/code&gt; statement in C#.&lt;/p&gt;
&lt;p&gt;Two things worth knowing here that aren&amp;rsquo;t obvious from examples: product assemblies don&amp;rsquo;t need to be referenced at all — they&amp;rsquo;re already loaded — and project-specific assemblies only need to be declared once, even if you use types from that assembly across many lines. Declaring the same reference three times doesn&amp;rsquo;t multiply anything at runtime; it&amp;rsquo;s just inherited copy-paste noise. It&amp;rsquo;s also entirely valid to skip declaring the namespace and use the fully-qualified type name inline instead — slightly more verbose per line, but it keeps the top of the file clean, which is exactly what the auto-generated DEEs behind Business Workflows do (more on that below).&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="DEE Structure"
src="https://image.j-roque.com/posts/20250821-howdodeeswork/DEEStructure.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class="relative group"&gt;Triggering DEEs: Automatic, Manual, and by Code
&lt;div id="triggering-dees-automatic-manual-and-by-code" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#triggering-dees-automatic-manual-and-by-code" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;DEEs trigger three ways.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automatically&lt;/strong&gt;, by being associated with an Action Group or with a Rule attached to some other MES entity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Manually&lt;/strong&gt;, through the UI, where you pick the DEE, supply parameters, and execute it directly.&lt;/li&gt;
&lt;li&gt;And &lt;strong&gt;from code&lt;/strong&gt;, when you need to invoke one explicitly:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class="language-cs"&gt;var serviceProvider = (IServiceProvider)Input[&amp;#34;ServiceProvider&amp;#34;];
Cmf.Foundation.Common.Abstractions.IAction deeRule =
    serviceProvider.GetService&amp;lt;Cmf.Foundation.Common.Abstractions.IAction&amp;gt;();
deeRule.Load(&amp;#34;CustomDEEAction&amp;#34;);
List&amp;lt;KeyValuePair&amp;lt;string, object&amp;gt;&amp;gt; parameters = new List&amp;lt;KeyValuePair&amp;lt;string, object&amp;gt;&amp;gt;();
if (this.Material != null)
{
    parameters.Add(new KeyValuePair&amp;lt;string, object&amp;gt;(&amp;#34;Material&amp;#34;, this.Material));
}
deeRule.Execute(parameters);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 class="relative group"&gt;Other uses
&lt;div id="other-uses" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#other-uses" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;DEEs aren&amp;rsquo;t confined to Action Groups. They surface throughout MES: Checklists, Event Rules, Timers, Smart Table validations, Label Printing, Future Actions, Sort Rules, Maintenance Plans, Business Workflows, Automation Scheduled Action, and more. If you&amp;rsquo;ve built a Business Workflow, you&amp;rsquo;ve already created a DEE without necessarily realizing it.&lt;/p&gt;
&lt;p&gt;That last one is worth seeing directly. Configure a workflow that, say, automatically moves a material to the next step whenever it changes into a specific step name — and MES generates the DEE for you behind the scenes, wired up to all the relevant Action Groups automatically. Open the generated code and you&amp;rsquo;ll notice it skips &lt;code&gt;UseReference&lt;/code&gt; entirely: everything it touches is product assembly and product namespace, so there&amp;rsquo;s nothing to declare. It&amp;rsquo;s a useful reference for what &amp;ldquo;clean&amp;rdquo; DEE code looks like when you don&amp;rsquo;t need any project-specific references at all.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Business Workflow"
src="https://image.j-roque.com/posts/20250821-howdodeeswork/BusinessWorkflow.png"
&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Business Workflow DEE"
src="https://image.j-roque.com/posts/20250821-howdodeeswork/BusinessWorkflowDEE.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class="relative group"&gt;Classification: Be specific
&lt;div id="classification-be-specific" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#classification-be-specific" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The classification field is easy to overlook, plenty of engineers never touch it, but it matters the moment a customer is autonomous enough to build their own Rules. Tag a DEE with a free-text classification, say &lt;code&gt;FreeWork&lt;/code&gt;, and then scope a Rule to only display DEEs with that same classification. When the user goes to build the rule themselves, they only see the options that are actually relevant to it, instead of scrolling through every DEE in the system looking for the right one. It&amp;rsquo;s a small feature, but it&amp;rsquo;s the difference between &amp;ldquo;the user can safely self-serve&amp;rdquo; and &amp;ldquo;the user picks the wrong DEE because nothing stopped them.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;There are also classifications that are important like, for executing DEEs from ConnectIoT they need to be with that scope.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Behind the Scenes: Versioning, Storage, and Caching
&lt;div id="behind-the-scenes-versioning-storage-and-caching" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#behind-the-scenes-versioning-storage-and-caching" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;Every save to a DEE creates a new version rather than overwriting the last one.&lt;/p&gt;
&lt;p&gt;That gives you &lt;strong&gt;rollback&lt;/strong&gt;, &lt;strong&gt;comparison between versions&lt;/strong&gt;, and &lt;strong&gt;history&lt;/strong&gt;, a clear answer to &amp;ldquo;who changed this, when, and what exactly changed&amp;rdquo;. A question every implementation team has had to answer under pressure at some point. Setting a different version as effective is a straightforward state flip between records; nothing destructive happens to the versions you&amp;rsquo;re not using.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="Comparison DEE"
src="https://image.j-roque.com/posts/20250821-howdodeeswork/ComparisonDEE.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Underneath, DEEs live in their own database schema, aptly named &lt;code&gt;DEE&lt;/code&gt;. The core table, &lt;code&gt;Dee.T_Action&lt;/code&gt;, holds one row per version: the DEE&amp;rsquo;s name, description, the action code itself, the assembly name it compiles to, the validation code, and the validation assembly name. Every version of every DEE, product or custom, it makes no distinction, lives in this same table and goes through the same caching pipeline.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="DEE Table Schema"
src="https://image.j-roque.com/posts/20250821-howdodeeswork/DEETableSchema.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Loading works differently than most engineers assume. Every DEE is loaded into cache when the host starts, not lazily, all of them, up front. When someone changes a DEE&amp;rsquo;s effective version or its Action Group associations, the cache detects the assembly name has changed and reloads just that entry. What actually sits in cache is exactly what&amp;rsquo;s in the database table: name, action code, validation code, effective version. No compiled artifact yet.&lt;/p&gt;
&lt;p&gt;The DLL only gets compiled — and written to a temporary folder inside the host — the first time the DEE actually &lt;em&gt;executes&lt;/em&gt;, not when it&amp;rsquo;s imported and not when master data is loaded. This answers a question that came up directly in the session: deploying a package that reinstalls every DEE version, even ones that haven&amp;rsquo;t functionally changed, does not multiply the number of DLLs sitting around. You accumulate rows in a table; you don&amp;rsquo;t accumulate compiled artifacts until something actually runs.&lt;/p&gt;
&lt;h2 class="relative group"&gt;DEEs vs Services: Choosing the Right Tool
&lt;div id="dees-vs-services-choosing-the-right-tool" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#dees-vs-services-choosing-the-right-tool" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;This is the recurring architectural fork: extend the product through a DEE, or build a proper service. Both are valid, and the right choice depends on scale and who needs to touch the logic afterward.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;DEEs&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Services&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Only way to extend product services at all; highly reusable for small blocks; enable/disable straight from the UI; flexible updates without a new package delivery; easier to debug on the client side&lt;/td&gt;
&lt;td&gt;Better for large or complex codebases; cleaner for end users (logic hidden from the UI); less prone to unsynced client-side edits; easier to track execution history; supports service-level security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Client-side changes can get lost if not synced back to the repository; not ideal for large or complex logic; performance overhead on first execution; harder to track execution history; no service-level security for individual DEEs; vulnerable to breaking silently if the product renames namespaces or classes&lt;/td&gt;
&lt;td&gt;Requires a new package delivery for every change; harder to debug directly on the client side&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The practical middle ground the session landed on: DEEs are the only lever you have for extending product services, for that use case there&amp;rsquo;s no real decision to make. For everything else, size and ownership decide it. Large, structured logic that benefits from real code organization belongs in a service. Small, toggle-able, client-adjustable logic belongs in a DEE.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Why You Can&amp;rsquo;t See DEE Execution Time in Service Performance
&lt;div id="why-you-cant-see-dee-execution-time-in-service-performance" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#why-you-cant-see-dee-execution-time-in-service-performance" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;This is the practical cost of the DEE/Service trade-off that catches people off guard: history tables tell a very different story depending on which one you used.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;DEEs&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Services&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Service History entry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Execute Action&lt;/code&gt; — the same name every time, regardless of which DEE ran&lt;/td&gt;
&lt;td&gt;The actual service name (e.g. &lt;code&gt;TrackInMaterial&lt;/code&gt;, &lt;code&gt;ComplexDispatchAndTrackInMaterial&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where to find real detail&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dbo.T_OperationHistory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dbo.T_ServiceHistory&lt;/code&gt; (already shows it)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Because every DEE shows up in &lt;code&gt;T_ServiceHistory&lt;/code&gt; as a generic &lt;code&gt;Execute Action&lt;/code&gt;, the history UI will show as a ExecuteAction which you will then have to drill down to check what was the DEE action called.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Service Performance&lt;/strong&gt; report, which without observability can sometimes be helpful to out what are the services taking with a worse performance, is blind to what DEE action is the actual bottleneck. The user will have to the go into &lt;code&gt;T_OperationHistory&lt;/code&gt; and join the two tables to get a real answer. That&amp;rsquo;s a genuine operational disadvantage of DEEs worth knowing about before you lean on one for something performance-sensitive.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="DEE History"
src="https://image.j-roque.com/posts/20250821-howdodeeswork/DEEHistory.png"
&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="DEE History2"
src="https://image.j-roque.com/posts/20250821-howdodeeswork/DEEHistory_2.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 class="relative group"&gt;Best Practices That Actually Matter
&lt;div id="best-practices-that-actually-matter" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#best-practices-that-actually-matter" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;h3 class="relative group"&gt;Try/Catch Has Limits You Don&amp;rsquo;t Control
&lt;div id="trycatch-has-limits-you-dont-control" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#trycatch-has-limits-you-dont-control" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;Catching an exception inside a DEE and quietly moving on works fine, right up until the method you called wraps its body in &lt;code&gt;StartMethod&lt;/code&gt; / &lt;code&gt;EndMethod&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Those two calls maintain an internal stack of every start and end call made during the transaction. Swallow an exception thrown from inside that wrapped method, and the stack ends up with more starts than ends. That mismatch is exactly the cryptic error message a lot of engineers have hit without ever understanding where it came from.&lt;/p&gt;
&lt;p&gt;You can catch and re-throw a different, friendlier exception, that&amp;rsquo;s fine, because the transaction still fails the way the product expects. What you can&amp;rsquo;t do is catch and &lt;em&gt;suppress&lt;/em&gt; an exception coming out of a product method that uses &lt;code&gt;StartMethod&lt;/code&gt;/&lt;code&gt;EndMethod&lt;/code&gt;, and expect the transaction to close out cleanly. The validation happens at the end of the transaction, and it will catch the mismatch even if your code didn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;&lt;figure&gt;&lt;img
class="my-0 rounded-md"
loading="lazy"
decoding="async"
fetchpriority="low"
alt="DEE Throw"
src="https://image.j-roque.com/posts/20250821-howdodeeswork/DEE_Throw.png"
&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 class="relative group"&gt;Transactions Don&amp;rsquo;t Roll Back the Outside World
&lt;div id="transactions-dont-roll-back-the-outside-world" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#transactions-dont-roll-back-the-outside-world" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;The assumption &amp;ldquo;if something fails, everything gets rolled back&amp;rdquo; is true only for what&amp;rsquo;s inside the database transaction. Anything that already crossed a system boundary is permanent the moment it happens, transaction outcome notwithstanding.&lt;/p&gt;
&lt;p&gt;A DEE on &lt;code&gt;CreateMaterial.Post&lt;/code&gt; sends a message to an ERP system, then goes on to move the material to another step. If that move fails, the material creation rolls back, but the ERP already received the message. Create the material again and you&amp;rsquo;ll send that message a second time. If the ERP isn&amp;rsquo;t built to deduplicate, you now have two systems out of sync because one of them thinks something happened twice. The same happens when you send a message bus message to Connect IoT. The machine will receive the message. If the transaction aborts, the message will have already be received by the machine.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Changes are rolled back due to failure, except the ones that already crossed a system boundary. Everything inside the transaction is undone; everything the outside world already saw stays true.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Where possible, perform the irreversible, externally-visible action &lt;em&gt;last&lt;/em&gt; in the sequence, so a failure means nothing external happened yet. Where that&amp;rsquo;s not possible — timers, integration entries, anything asynchronous by nature — agree on a message format with a unique identifier the receiving system can use to ignore duplicates.&lt;/p&gt;
&lt;h3 class="relative group"&gt;Keep Your Output Objects Thin
&lt;div id="keep-your-output-objects-thin" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#keep-your-output-objects-thin" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h3&gt;
&lt;p&gt;The output object a DEE returns gets serialized into JSON at the end of execution. If that output includes an entity like &lt;code&gt;Material&lt;/code&gt; with every property loaded — product, steps, facility, area, calendars, all of it — you&amp;rsquo;re paying for that JSON size whether or not the caller needs any of it. It compounds: loading a fully-populated entity is also extra database round trips you may not need in the first place.&lt;/p&gt;
&lt;p&gt;The fix is simple, load only the properties your DEE actually needs, and if you need a richer object to work with internally, build a separate object for that and keep it out of the output entirely. A good developer is a minimalist.&lt;/p&gt;
&lt;h2 class="relative group"&gt;Final Thoughts
&lt;div id="final-thoughts" class="anchor"&gt;&lt;/div&gt;
&lt;span
class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"&gt;
&lt;a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#final-thoughts" aria-label="Anchor"&gt;#&lt;/a&gt;
&lt;/span&gt;
&lt;/h2&gt;
&lt;p&gt;The tutorials and documentation get you going, but some decisions take years to come back to bite you. In this blog posts we tried to check the major hurdles and pain points and also performing a comparative analysis.&lt;/p&gt;
&lt;p&gt;After this you are ready to be even better at extending the MES system.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This blog post was based on a talk by Nicole Ribau in 2025-08-21 @CM-Portugal&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded>
<enclosure url="https://j-roque.com/posts/20260724-howdodeeswork/featured.png" type="image/png" length="0"/>
<media:content url="https://j-roque.com/posts/20260724-howdodeeswork/featured.png" type="image/png" medium="image"/>
</item>
</channel>
</rss>