Coder Social home page Coder Social logo

flow-recursion's Introduction

The Dangers of Recursion with Before-Flows

Before Flows are neat - they allow efficient updates on the same object, without having to do an extra commit to the database. However, a note from the Architect's Guide says that they will never support recursive execution.

On one hand, who cares about recursion? It hurts your head, right?

On the other hand, this introduces a sharp-edged restriction into your system that you really need to be aware of. If your system causes some accidental recursion (even if it's only once), your Flow won't do what you expect. Or, at least, that was my concern. So, here's a project I built to test the theory.

You can download this and try it for yourself! Especially if it turns out that I've done something dumb, but I think it's right.

The Flow

I built a Before Flow on Lead which put Leads with a Rating of "Hot" onto a Queue called "Hot Queue". More precisely, if the Company contains "Flow" and Rating is "Hot", then query for a Queue called "Hot_Queue", and assign the owner over to that Queue. Here are the Apex tests for it:

@IsTest
static void flowSetsOwner() {
    Lead testLead = new Lead(Company = 'ACME Flow', Rating = 'Hot', LastName = 'Duck');

    Test.startTest();
    insert testLead;
    Test.stopTest();

    testLead = [SELECT OwnerId FROM Lead WHERE Id = :testLead.Id];

    System.assertEquals(hotQueue.Id, testLead.OwnerId);
}
@IsTest
static void flowSetsOwnerOnUpdate() {
    Lead testLead = new Lead(Company = 'ACME Flow', LastName = 'Duck');
    insert testLead;
    testLead = [SELECT OwnerId FROM Lead WHERE Id = :testLead.Id];

    System.assertNotEquals(hotQueue.Id, testLead.OwnerId);

    Test.startTest();
    testLead.Rating = 'Hot';
    update testLead;
    Test.stopTest();

    testLead = [SELECT OwnerId FROM Lead WHERE Id = :testLead.Id];

    System.assertEquals(hotQueue.Id, testLead.OwnerId);
}    

These tests pass

The Process Builder

I built a Process Builder to do a same-object update on Lead the old way. If the Company contains both "Unicorn" and "PB", then set the Rating to "Hot". Here's the Apex test for it:

@IsTest
static void pbSetsHot() {
    Lead testLead = new Lead(Company = 'ACME Unicorns PB', LastName = 'Duck');

    Test.startTest();
    insert testLead;
    Test.stopTest();

    testLead = [SELECT Rating FROM Lead WHERE Id = :testLead.Id];

    System.assertEquals('Hot', testLead.Rating);
}

This test passes

The Apex

Finally, I built an Apex trigger to do a before insert/update assignment. If the Company contains "Apex" and Rating is "Hot", then query for a Queue called "Hot_Queue", and assign the owner over to that Queue. Here's the Apex test for it:

@IsTest
static void apexSetsOwner() {
    Lead testLead = new Lead(Company = 'ACME Apex', Rating = 'Hot', LastName = 'Duck');

    Test.startTest();
    insert testLead;
    Test.stopTest();

    testLead = [SELECT OwnerId FROM Lead WHERE Id = :testLead.Id];

    System.assertEquals(hotQueue.Id, testLead.OwnerId);
}

This test passes

What about the Flow Unicorn PB?

What happens if we combine PB updating the Rating to "Hot", and then Flow to assigning it to the Queue?

@IsTest
static void pbAndFlowSetsHot() {
    Lead testLead = new Lead(Company = 'ACME Flow Unicorns PB', LastName = 'Duck');

    Test.startTest();
    insert testLead;
    Test.stopTest();

    testLead = [SELECT Rating, OwnerId FROM Lead WHERE Id = :testLead.Id];

    System.assertEquals('Hot', testLead.Rating);
    System.assertEquals(hotQueue.Id, testLead.OwnerId);
}

This test fails.

The execution order is:

  1. Lead insert begins
  2. Flow runs, no need to reassign Owner as Rating is not Hot
  3. PB runs and updates Rating to Hot
  4. Flow doesn't run again because that would be recursion

Won't someone think of the Apex Unicorns?!

What about if we try the same recursion, but with Apex Unicorns instead of Flow:

@IsTest
static void pbAndApexSetsHot() {
    Lead testLead = new Lead(Company = 'ACME Apex Unicorns PB', LastName = 'Duck');

    Test.startTest();
    insert testLead;
    Test.stopTest();

    testLead = [SELECT Rating, OwnerId FROM Lead WHERE Id = :testLead.Id];

    System.assertEquals('Hot', testLead.Rating);
    System.assertEquals(hotQueue.Id, testLead.OwnerId);
}

This passes. In Apex, recursion control is up to the developer.

Conclusions

This doesn't mean that Flow is bad, but there is certainly one scenario where you will have to be very careful: migrating an org with a bunch of same-object PBs over to Flow. If you're half-migrated, then the PBs can interact with the Flows in the way above and give you results that weren't what you were expecting.

I think of before triggers in Apex as being for enforcing database invariants. I know that if I've wired Hot Leads to be assigned to the Hot Queue with a before trigger, I can just forget about it. There will be no Hot Lead that isn't on the right queue. With Before Flows, this abstraction becomes leaky.

In the presence of a complicated system where automation can bounce from one object to another, recursive loops that stop Flow may be very hard to spot. And sometimes recursion isn't your choice - it may be thrust upon you be packages or by legacy code. So, you may very well be scratching your head about this one in the future.

flow-recursion's People

Contributors

aidan-harding avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.