A contract written in blood

Yeah, I bet that headline got your attention. I'm back! I'm married now and therefore I once again have free time. Be honored: I have chosen to use this free time to tell you about exceptions and simulated contracts.
Not to insinuate, of course, that exception handling in OutSystems is not an interesting or attention-grabbing subject in itself. For many, many years now OutSystems developers have been designing, building, and relying on error-handling systems for their projects which rely solely on the knowledge of their existence to operate correctly within a production factory. That is, if I write a reusable action in OutSystems which is capable of throwing an unexpected error, my options are to:
- catch that error myself, take appropriate action, and set output variables to allow the consumer logic to determine what to do next based on a boolean value and an error message, or...
- allow the error to bubble up to the consumer, passing control to the nearest available exception handler in the call stack.
Then through documentation, emails, repeated frustrated Teams messages during code reviews, calls, and perhaps dreaded in-person meetings (if you have an office), you need to hammer home a social agreement with your development team to stick to the error handling pattern and make sure to handle errors in your logic no matter what.
So which is better? That's the neat part: nobody knows!

The reason nobody knows is that there is no truly enforceable contract for exception handling in OutSystems. While this may be surprising to you, understand that it is not at all a design flaw in OutSystems itself, and likely it was not even a design consideration because OutSystems apps are all built on the back of C#. Unlike Java, C# does not have a compiler-time check for exception handling enforcement. Java requires that the invoker of a method with exceptions defined in the throws
signature must have said method in a try-catch block to explicitly handle those exceptions or some common ancestor type for those exceptions. C# was explicitly designed not to require this. This is called checked (like Java) exceptions vs unchecked (like C#) exceptions [1].
Wow, look at that! I've included a citation as well as a meme! Maybe this isn't some dullard's journal after all?
The point I am trying to make is that when exceptions are unchecked, you get a lot of benefits like flexibility and a massive reduction in the amount of code required to implement something simple and low-stakes. When your exceptions are checked, you force developers to write try-catch blocks all over the place and are likely to end up with vacuous error checks because the developer may not actually have a good way to recover from a NullPointerException.
Wait... OutSystems doesn't support the concept of null either. I wonder if... could it be that OutSystems removed null to avoid unchecked null pointer exceptions happening everywhere all the time?

There's really no need to test my theory, because I am obviously right about everything so far, but let's do it anyways. You may have noticed, as I have, that every OutSystems application (at least, front-end application) you create will have a global exception handler defined by default. This depends on the application template you're using, but I am assuming you're using an OutSystems UI standard template where for my entire career an OnException
handler has been dutifully waiting for me in the Common
web flow of every newly created app.
Based on C# documentation [2] we should be able to delete this OnException
handler and still be able to compile the application, however when we throw an unhandled exception in our test app, we should be able to watch the application die. When I went to test this, I actually watched it die earlier than expected because I wasn't logged in and an unhandled security exception got thrown before I could ever hit my button that was supposed to throw the unhandled exception. After some maneuvering, though, I was able to get in and throw my own unhandled exception which did indeed kill the application.

We are now all better OutSystems engineers for knowing this. Unfortunately, this doesn't really address the main problem presented in this article which could be distilled to, "what's the best way to do error handling in a complex OutSystems factory?" Time for the big reveal:
I don't know. But what I DO know is a pattern to simulate typed exceptions in OutSystems which will help you at some point in your career. Let's start with the basic premise: I have an action that can throw a number of expected errors whether that be validation errors, security exceptions, etc... The consumers/users of this action may need to take specific action based on the error that occurred, which means a basic UserException
or IsSuccess
output parameter with a human readable message is not particularly useful.
To make things more useful, we're going to lean on two basic axioms. First, exceptions in OutSystems are not capable of passing anything other than text. When you throw or catch an exception, you only have the ExceptionMessage
parameter available. Second, when we need to execute specific logic based on a value from a set of options pre-determined at design-time, we use static entities in OutSystems. You are able to set the identifier of a static entity to be of type Text
and then use that value in a manner almost identical to how you would use an Enum
in other languages.
We can somewhat simulate typed exceptions using this information. The outline of the solution is to:
- Identify all the potential errors that a consumer of this action would need to take specific action on.
- Create a static entity in which all of the errors identified in step 1 are represented with text IDs that uniquely identify the error.
- To throw a "typed" exception, set the
ExceptionMessage
parameter usingEntities.ErrorEntity.ErrorRecord
. This will pass the identifier string to the consumer catching the exception. - Expose a public action which parses exception messages and determines whether the exception is an expected "typed" exception and returns which it is.
- Use the action from step 4 in your exception handler flow in the consumer module and take appropriate action based on the type of exception thrown.
Another design consideration which I have employed to reduce the dependency surface for this solution is to just use Boolean outputs in the public action that parses exception messages so that the only required reference is said action and the static entity can remain private.
Does this actually solve the enforcement problem? Absolutely not, you cannot fundamentally alter how exception handling works in OutSystems and prevent developers from ignoring whatever error-passing methodology you've employed. However, this method does have the benefit of protecting code from being executed that should not be executed post-exception because the exception, no matter what, will pass execution from the primary logic flow up the call stack until it finds an exception handler.

So there you have it. As usual, I am not going to write out the implementation for you as there's just not enough time in a Saturday for that. Would you like to see more articles like this, or do you want me to talk about something else? Reach out to me if you have questions, suggestions, or you just want to chat! Drop a comment below, subscribe, send me to your nerdy friends.
I hope you all have an exceptional day!
Citations
[1] Stack Overflow. “Checked and Unchecked Exception in .NET.” https://stackoverflow.com/questions/3761844/checked-and-unchecked-exception-in-net
[2] Microsoft Learn. “Exception Handling (C# Programming Guide).” https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/exceptions/exception-handling