Idempotency and purity for modern Pega apps

I want to provide a quick treatment for Pega practitioners since the idempotency/purity topic has come up a few times now — both here on the forums and during my interactions in the field. This is because the Constellation architecture in Pega Infinity introduces a couple of significant changes:

  1. The back-end drives the front-end, and interactions are stateless.

  2. The OOTB Constellation end-user UX is written in React, which imposes purity requirements.

Let’s start with idempotency. The essence of it can be simply grasped with Google’s elevator analogy:

Think of the “up” button on an elevator. If you press it once, the elevator is called to your floor. If you press it ten more times while waiting, the result is the same—one elevator still comes to your floor. Pressing the button multiple times doesn’t summon ten separate elevators. In contrast, adding an item to a digital shopping cart is usually not idempotent. If you click the “Add to Cart” button five times, you’ll likely end up with five of that item in your basket.

For a software example, consider making a forum post. Ideally, an accidental extra click on the Create conversation button shouldn’t result in a duplicate post. Exactly how this is achieved is less important than the effect: invoking the action N times leads to the same state as invoking the action once.

Now for purity: this can be intuitively understood as a stronger form of idempotency. If “normal” idempotency only requires that simultaneous invocations result in the same state, “purity” requires that invocations never change state to begin with. The React docs have an article called “Components and Hooks must be pure” which goes into more detail:

A pure component or hook is one that is:

  • Idempotent – You always get the same result every time you run it with the same inputs – props, state, context for component inputs; and arguments for hook inputs.

  • Has no side effects in render – Code with side effects should run separately from rendering. For example as an event handler – where the user interacts with the UI and causes it to update; or as an Effect – which runs after render.

  • Does not mutate non-local values: Components and Hooks should never modify values that aren’t created locally in render.

Whenever I see “pure function” I think of a mathematical function:


f(x) = 2x^2 + 3x + 4

This function just takes some input, does some number crunching, and returns some output. You can see how running it multiple times with the same x will only ever produce the same result, and it won’t affect anything else (“no side effects”). Contrast this with a typical impure function:


do_stuff(A, B, C)

{

draw(A);

email(B);

erase(C);

}

This function is not idempotent (call it multiple times and A will be drawn multiple times), it has side effects (B gets emailed), and it mutates non-local values (deletes C).

There’s a lot more to be said on these topics, do read up if you’re interested. But I find this surface-level understanding is usually adequate, since there are only a couple of areas in a typical Pega build where these concepts apply: custom components and assignment pre-processing.

For custom components, the React docs spell out the requirement unambiguously: “Components and Hooks must be pure.” Now in my experience this requirement is met with little effort since the tooling, docs, and best practices of both the React and DX Components ecosystems tend to steer things in the right direction. In fact, I find that LLM’s usually Do The Right Thing here. Nevertheless, if you see yourself creating a bunch of components, I would consider that a nudge in the direction of studying these concepts in a bit more depth.

Assignment pre-processing is even simpler still, I wrote about this elsewhere. Briefly: make sure assignment pre-processing is idempotent. In practice I find myself reaching for pre-processing far less with Constellation, relying mostly on data pages and relationships with the occasional utility workflow step. The canonical example is the “search and select” pattern, which can be set up in minutes with Constellation without any need for pre-processing actions triggered by on-change refreshes.

Which leads me to my closing point: this stuff isn’t as new as it may seem. Take that search and select pattern: before Constellation, I’d usually have a pre-processing activity that would fetch the search results, and the first thing it would do is erase the clipboard page which stored those results. Otherwise the list of results would grow endlessly every time the user clicked Search and triggered a refresh. That kind of activity implements a rudimentary form of idempotency — and it’s something I almost never have to bother with anymore.

So what initially may seem like a source of friction in Constellation is in effect a streamlined codification of best practices. That’s just one example of how Center-out solution design really can lead to better apps delivered faster vs. top-down design.

Enjoyed this article?

See suggested articles from our Constellation 101 series and view all our Knowledge Shares from our User Experience Expert Circle.

2 Likes

Another great edition to our community @peter_bessman. Thanks! I’ve added to the Constellation 101 - Knowledge Share - Pega Forums :slight_smile:

2 Likes

Thanks Marc!

I tried to be brief; upon giving this a quick re-read, I want to add a footnote regarding this aspect of the Constellation architecture:

  1. The back-end drives the front-end, and interactions are stateless.

One might argue that this is a little too brief… what I’m getting at here is principally the contrast between “traditional” web apps and single-page apps, which largely models Pega’s own UX architectural evolution (though the DX API introduces some major differences that enable a very robust Center-out strategy). So if anyone was mystified by that line, this is the topic to research.

1 Like