This is likely my last post in a series documenting my Mule programming style. If you haven't checked out my other posts in this series, you can check them out here, here, and here. In this post, I'll be going over why the choice router gets abused, why that's a problem, and what you can do to fix it. Let's get into it.
There are a few of my colleagues at Mountain State Software Solutions that know about my aversion to using the choice router. I'm pretty open about it. Ever since I started building integration solutions using MuleSoft about 3 years ago, and saw something like this:
I knew it was going to be a problem. And once I started up my blog, I knew that at some point I was going to need address this issue. So, here is my plea to my fellow Mule developers. My hope is that in just a few minutes of your time I can convince you to stop abusing this highly-flexible component and instead use more specialized ones to accomplish your task.
Why People Abuse the Choice Router
The reason people abuse the choice router is no mystery to me, because I used to do it myself. It's quite simple:
- The choice router is the first thing Mule developers learn to use that can be used to implement conditional logic.
- The choice router more closely resembles the if-then-else statement that programmers are very familiar with. Java/Python/etc. programmers that are new to Mule will lean on the choice router because it feels so familiar.
The problem is the choice router isn't meant to be used in every situation you would use an if-then-else statment. Instead, the choice router is meant to be used when you need to use conditional logic that will dictate how your message will be routed through your program. Instead of focusing in on positive examples of this, let's instead focus on a couple of popular examples that violate this, and see how they can be improved.
Conditionally Setting Variables
Here's the situation: you need to store some kind of value in a variable, but depending on the current state of the running program, that variable could take on different values determined by conditional logic. When you use the choice router for this it looks like this:
Like I said earlier, the problem is that there's clearly no message routing going on here. If you take out the choice router, the flow of the message is the same. I also dislike this approach because the entire logic for setting the single variable is stretched out among 3 separate components. In order for me to fully understand how
x is set, I need to see how the choice router is configured, and then check both set-variable components. Not only that, but I need to make sure it's actually
x that is being set in all of the set-variable components and not some other variable. This requires additional effort from the reader where it's not necessary. This could be easily implemented in the set-variable component instead with a little bit of DataWeave. Here's an example:
<set-variable value='#[if(condition) "1" else "2"]' doc:name="x" doc:id="..." variableName="x" />
This results in our canvas looking like this:
Which is so much cleaner. Now, when other developers are reading your code they know exactly what they need to know: you're setting a variable called
x. If the reader wants or needs to dive into the possible values that
x could be, they have the option to do that, but you've afforded them the ability to ignore that detail so they can focus on something more important. Ultimately, the reader only needs to go into the set-variable component to figure out how... the variable is set. Makes a lot of sense, right?
If you're using Mule 3, where DataWeave is a bit more cumbersome to access in the set-variable component because you need to do so through MEL, a great alternative is to use a ternary expression instead, which would look like this:
condition ? "1" : "2"
There are other scenarios where you need nested if-then-else statements. When the logic for how to set a variable starts to get more complicated, the answer is not to reach for more choice routers, and it's not a good idea to stuff all that logic into the single line available in the set-variable component. Instead, you should reach for the tranform component. Just make sure your output is set to the variable to which you want to write, not the payload. For example:
<ee:transform doc:name="Set x" doc:id="..." > <ee:message > </ee:message> <ee:variables > <ee:set-variable variableName="x" ><![CDATA[%dw 2.0 output application/java --- if(<condition1>) "1" else if (<conditional2>) "2" else "3"]]></ee:set-variable> </ee:variables> </ee:transform>
Also, make sure you give the component a good name, like "Set x", so that readers know immediately that you're setting a variable, and not transforming the payload.
If you're using DataWeave 1.0, the same concept applies, you just implement using
otherwise, if that's your thing).
Conditionally Throwing an Exception
There are situations during a flow where you will want to check to make sure certain elements of the message hold true, and if they don't, you'll want to terminate the flow by throwing an exception. Here's how I often see this implemented (example in Mule 3):
Where you'll see something like this in the Groovy component:
throw new java.lang.Exception();
This is always a bad idea, in my opinion.
First, you either need to add a logger that's really not saying anything except "Validation passed, continuing," or you need to contain the rest of the logic for the flow in that branch of the choice router, and that's sloppy, This strategy starts to get obsurd when you begin chaining validations. Check the first image in post to get an idea of what that might look like.
You might be thinking, "You could just implement the conditional logic in the scripting component where you're throwing the exception." That's cetainly better than using a choice router with a useless logger, but we can do better. Why not use a component that was designed to do exactly what you're trying to accomplish?
Since the validation component was introduced in Mule, it no longer makes sense to use scripting components to throw exceptions. Like in the example above, you would need three separate components to implement the logic (or a
scripting component with the logic within it). With the
validation component, you can accomplish the same thing with a single component that was tailor-made to handle this situation. It lets you specify the validation check, what part of the message you're validating, what exception to throw if the validation doesn't pass, and the message associated with the exception, all in one place. This means your flow can look like this instead:
The only time I could see a scripting component being viable for throwing an exception is if the constructor for the exception takes more than just a message. I haven't run into that use case myself, but it's possible. In any case, you should be opting to use the validation component first, and if it doesn't fit your needs, then go with something more flexible.
A brief aside...
Just to make sure I'm covering all my bases here... You might argue that if you're executing a bunch of logic in a scripting component, it might be appropriate to throw an exception from within it. However, I haven't really seen the need to use a scripting component ever since I've become more comfortable with DataWeave. But, if you're 100% sold on not using DataWeave for what you want to script, just use Java. There are numerous advantages:
- If the logic needs to be reused across applications (like perhaps another Java application doesn't have the ability to easily call upon another language), that can be easily done with Java
- With the new Java module for Mule 4, calling Java from Mule is easier than ever (check out my videos on this here and here),
- If you care about performance, Java is probably faster than any of the intepreted languages offered by the scripting component.
In this post I've discussed why the choice router is probably one of the most abused components in all of Mule. People newer to Mule will latch on to the component because it feels so similar to if-then-else statements in other languages, but it's really not meant to serve that purpose. Using choice routers to conditionally set variables puts an unecessary mental-burden on the readers of your code, and you should instead opt for a set-variable component and conditionally set the value using DataWeave. In the case of conditionally throwing exceptions, the choice router doesn't make sense in this scenario, either. You should be opting to use the validation component, unless your exception contructor takes in more than just a String. If that's the case, a scripting component may be acceptable, but you should probably just use Java. When it's all said and done you should never be using the choice router unless you're using it to route messages. Next time to reach for it, ask yourself: "Am I conditionally routing the message with this, or doing something else?" If you're doing something else, take some time to figure out a better way!
Credit for the header photo goes to my friend and photographer, Christopher Caldwell. You can check out more of his work here.