When to use Try/Catch?

Hi - What are your views on when to use Try/Catch construct in Flow services? (The Try/Catch construct is defined in GEAR 6 page 17)

  1. Some people have the viewpoint that all user-created services should have their code wrapped by a ‘Try/Catch’ construct. (Even if all the ‘Catch’ block does is log an error and exit). For eg:

TRY
  doThis
  doThat
CATCH
  getLastError
  logError
  EXIT with failure 
  1. Others say use Try/Catch only for “top-level” services – i.e. a services invoked by external users, or “interface” service – i.e. services which make a connection to another system (Even if all the ‘Catch’ block does is log the error and exit.) Example: same as above.

  2. Others hold the view that Try/Catch should only be used in a service if the built-in IS exception handling is not sufficient. ie. The catch block needs to do something different. For example below, it rolls back a failed transaction:


TRY
  doThis
  doThat
CATCH
  getLastError
  logError
  [b]doRollback[/b]
  EXIT with failure 

Which of these 3 options - or other alternative - do you favor?

Regards,
Sonam

Sonam,

I’m in Camp 1. I use try-catch for all of my Flow services. Usually, I’m building them individually and having try-catch makes that process go more smoothly. Also, if you standardize your catch-block structure or place a standardized logging service call there, consistency of error handling is improved across the organization.

I’m sometimes asked to assess the quality of work performed by other consulting firms. When I find massive numbers of Flow services with nary a try-catch sequence in sight, that’s a sign that I’m dealing with rank amateurs in most cases.

Mark

Thanks Mark. It’s interesting you use Try/Catch in all services (including utility services?)

Where customized error logging is not used I stay in Camp 3 myself… ie I don’t use ‘Try/Catch’ unless the ‘Catch’ block adds more value than the built-in exception handler.

Where customized error logging must be used, I am in Camp 2 – a ‘Try/Catch’ block is used in ‘top level’ services (entry-points for transactions). Exceptions thrown downstream ‘bubble-up’ to the top-level and are logged with stacktrace. (Except for transformers IIRC).

A couple of disadvantages with Camp 1:

(1) The extra code makes errors possible – For eg: wrongly setting the exit condition in a sequence, or even simple transposition errors like:


TRY
  doThis
  doThat
CATCH
  getLastError
  EXIT with failure
  logService

(2) the Flow becomes a few clicks more difficult to read

From what I see of webMethods-built services, they are firmly in Camp 3.
Eg: wm.tn:receive (WmTN), sample.soap:buildRPC_sendHTTPSimple (WmSamples)

Regards,
Sonam

I would not necessarily consider built-in (or generated) Flow services to be the “gold standard”. Just look at the web services connectors for one bad example, although one that uses try-catch. :slight_smile:

I agree that it takes too many steps to configure a try-catch sequence structure. That’s usually the first thing that I do when I create a Flow, but it should be as simple as clicking a button or writing a macro and then clicking a button. I know certain WM developers who were lobbyign for this almost 3 years ago, but I don’t think we’ve had much in the way of Developer ease of use improvements since the 6.0 to 6.1 change.

Mark

i personally like to use try/catch in all of my services …actually thats the first thing i do (as mark specified) when i start a flow …
but recently the company ,which i work for has changed its try/catch standards …they want to just have try/catch only in the parent service …and any child flow service which it uses will just carry the error back to the main catch block.
but i guess creating each and every service with its own try/catch is the neat way of coding …

sri

If you have a parent and child flow service type of scenario, catching the exceptions in the child flow services also might improve the granularity of information you want to put in the log.
For example if you are parsing a file at different levels in the parent and the child flow services you can include more information in the log about the file/content which helps you debug.

Shubhro

Generally, I use approach 2, though I avoid use of try/catch whenever possible (use of exception-style programming is another debate). Just like Java/C++ code that doesn’t have every method using try/catch blocks, I use try/catch more judiciously. Usually, it’s the top-level services–if they need to catch errors, which is almost always.

I use try/catch in “lower” services only when it’s necessary (commit/rollback, close, etc.). I’ve never worked on a project or in a company where all user-created services had try/catch. It’s rare for one my utility services to use try/catch.

In a couple of frameworks I’ve worked with it was absolutely essential that lower-level services didn’t catch errors (unless they needed to to commit/rollback, release connections, etc.–and then they needed to rethrow the error). This is because the framework managed the logging and notification processes. Effectively a “top-level try/catch” approach. Errors were also managed by the framework to help manage retries and isolate logging (logging scattered everywhere becomes painful).

I think the key here is to have an approach for try/catch use, and understand when the standard approach needs to be overridden/disregarded.

Here’s the rules that I usually follow:

  1. Always use a Try/Catch sequence in a top-level service that is not invoked from a model.
  2. When not in a top-level service, if you are not going to do something useful when you catch the exception, then don’t use a try/catch at all. This is unneeded overhead.
  3. If you do catch an exception and you are not at the top of the call stack, then either handle it gracefully or return a more meaningful message to the caller.
  4. Always do a pub.flow:getLastError as the first statement in your catch block.
  5. Remember that a try/catch sequence in webMethods has the same variable scoping rules as a try / catch in Java (but no compile time checking for scope issues). So, if you need access to a variable in the catch block, make sure that you have defined it before entering the try block. As soon as you enter the catch block, any variables that were defined within the try block will not be available (except via the lastError/pipeline structure - but I typically avoid trying to code against this structure since you don’t really have access to it at design time).
  6. I like to break out exceptions into three “classes”:
  1. Exceptions that you are expecting to occur and can gracefully handle.
  2. Exceptions that you are expecting to occur, but cannot gracefully handle.
  3. Exceptions that you are not expecting to occur.

[/INDENT][INDENT]A bit more on each…
[/INDENT][INDENT]Exceptions that you are expecting to occur and can gracefully handle
[/INDENT][INDENT]In this case, you will take some sort of corrective action in your code (if needed) so that the exception is transparent to the client.

Exceptions that you are expecting to occur, but cannot gracefully handle
In this case, you cannot hide the fact that there is a problem. You will need to agree with the client on either passing back some sort of response code and message or throwing an exception of a particular type. Which method you choose to use will depend largely on the protocol that you are using and the capability / preference of the client. The client may choose to retry the transaction or take other actions on their own.

Exceptions that you are not expecting to occur
In this case, you can do nothing. This will be a fatal condition, however you still need to agree with the client on how the exception will get raised up. However, since the exception is of an unknown type (i.e. it was not preconceived that it might occur), there may be no safe corrective action that either you or the client can take.

Good discussion everyone.

Mark - thanks for the inputs… macro or code template functionality would be great. Actually if webMethods implemented some nifty ideas from Aspect Oriented Programming (For eg: “Do logging for all mycompany.print.* services with handler: …”), there would be some cool benefits.

Rob - a concise, sensible, and perfectly justified policy as usual. Quoting you again (just because it’s so good) :slight_smile:

Hi “jlammers” – that’s a good set of rules. Got a question about the 1st one: “Always use a Try/Catch sequence in a top-level service that is not invoked from a model.” – What’s special about services invoked from a model? Does BPM implement built in error handling?

This is a good discussion and thanks, Sonam, for kicking it off! It’s given me some food for thought and some ideas for improving my custom SOAP processor error handling.

Mark

Yes, there is error handling of sorts. You can designate a process-wide error step and/or you can define an error transition. What method you use really depends upon the situation. That being said, the important thing is that you probably want to mark the model as failed. I typically explicitly set this in the process wide error step or, if you don’t have a process-wide error step and your service throws an exception, then the process will automatically get marked as failed.

The reasons that I generally don’t put a try / catch in the services that I call from a model:

  1. Usually I write a wrapper service called stepNameImpl, so there’s not a whole lot going on that could produce an exception.
  2. In a business process, if I’m able to handle some exception condition gracefully, then usually I’ve already done that in a lower level service. If there are any remaining exceptions bubbling up, I probably can’t do anything useful with them, so I will have to mark the process as failed - in which case there is no reason for a try / catch.

As with anything, these are not hard and fast rules, but that is my thought process behind not using a try/catch for services invoked by models.

I think it really all boils down to - “Can I do anything useful with the exception?”

One last thought - the coarseness (or granularity) of your logic within the process model will also dictate which approach to take. Very coarse process models will likely just use a process-wide error step - more fine-grained ones will probably use some combination of process-wide error step and error transitions.

–jeff lammers

May I add, 2 little cents…

I have also used nested try/catch, to monitor stuff. Example: Some applications in our enterprise are flaky. Our scheduler consistly checks the health of the database, by pinging it. If dead, the scheulers, filepollers, triggers…are turned off and call bunch of other services.

The top level services may NOT neccessarily have try/catch. Child may be them as well. The logic of “Sequence” SUCCESS, FAILURE and DONE, may became tricky. We do use broker to lose the thread in these cases and do extensive filtering at trigger level.

Thanks Jeff and everyone else in this discussion.

I personally hate coming across every single service with try-catch when all it does is clutter things up. I treat flow try-catches just like java methods, sometimes it is appropriate for a method to need a try catch, other times it does not catch it and “throws” the exception back up to the parent. Some things are exceptional exceptions, others are expected exceptions… Using too much try-catch tends to hide or incorrectly handle the exceptional exceptions.

I treat it like writing java code: so generally NOT with everything surrounded in try-catch structures, so same with flow code. I’ve come across a couple of attempts at coding standards or code quality checklists that I’ve had to un-drum the obsession with try-catch in every single service.

Top level services are always useful ones to have try-catch as a “last resort catch and log the error” type thing.

If the exception is caught too low down due to excessive try-catches then your service might not blow up like it should. Steps like map services look ugly with try-catches…
To roughly summarise:

  • top level services
  • services where the error handling has business logic or more in it than simply logging the error
  • anything using explicit transaction management to allow for calling the rollback
  • generally network operations and things which are quite likely to blow up
  • services that return different values or set flags when an error within them occurs (e.g. you do an isDatabaseAlive service that returns a true false value)

regards,
Nathan Lee

Hello,
It is ofcourse common to see a set of sequences that assimalate the java like try catch. One thing that is not the same, is the scope in the initial excuting sequence. All values are rolled back to before the sequence was called and so even if you define the field before hand, it will only have its previous value (pipeline rollback).

I have not heard anyone specifically mention the Event Manger. Is it not suitable for separating the certain actions, like logging, that are asynchronous and auxillary to program flow? It almost sounded like sonam was partly speaking of this but maybe you were referring to something more powerful? I use it for logging exceptions through a class of services to a specific location. It saves about 4 lines of code per service.

I would like a retry (with preserve) construct and to have code-watches with triggers (wwt). The wwt is having an action triggered by a particular state in the FLOW being true. Pretty much it is emboding aspects of the publish and subscribe model we use to the FLOW code at the document AND field level with less kludge. You could have a separate pane that you assign a service to run when a certain condition is met.

I feel awry about nested sets of these. That was something that ate at me to do right one time. The region was small enough to not warrant a whole separate flow service, but its error handling was complex enough to not be solvable at the same level. Some values needed to be in place to preserve field mutations before a following step was executed. I did want to know some of the values and allow others to rollback. an example:

map a = 0

seq exit-on:success

  • seq exit-on:failure

– map b = 1

– seq exit-on:success
— seq exit-on:failure
---- map c = dynamicsql3 b

— seq exit-on:failure
---- map d = dynamicsql4 b

— seq exit-on:done
---- map b = -1 ## keep b; rollback c,d

– map ## operate on b, c, d

  • seq exit-on:done
    – map a = -1 ## keep a; rollback b

map ## operate on a, b

For the most part, I treat transient errors as hiccups, so I use instead a REPEAT step and let it run 3 times with a sane time between. If it really was simply a delayed connection or waiting file state, I would soon have proper access, otherwise I allow the service to error as if I was sure the resource would never be obtainable. That is a wrapper to the normal call and the parent will handle any needed exception situations. It could be a second wrapper with a single seq exit-on:done that did a boolean return. That in fact is how I setup service that I would only do a log in error and continue the rest of the execution. The logging would be dumped off to Event Manager.

If you are going to include the try-structure in a recursive service, try to limit the re-entry point to one location. I just generally also try to not have mutually recursive definitions as that is too magical for me in all but very simple cases. Good day.

Yemi Bedu

Hi Nath - good to see you back.

Hi Yemi -

Pipeline rollback is a very important point that distinguishes sequences with ‘exit-on: SUCCESS’ from other sequences or from Java services – it’s good you raised it Yemi.

Thanks for also mentioning raising Event manager - it’s one of those things I kept intending to investigate and use, but somehow never got around to doing. I was on a training course last week, and a person there described using Event manager just as you do – to subscribe to exceptions and log them uniformly in a custom logging framework. It would be good knowing your experience and gotcha’s using event manager for logging.

I know only a bit of Aspect Oriented Programming (AOP)…
(Good description here: http://en.wikipedia.org/wiki/Aspect-oriented_programming)
While AOP is probably more powerful, what webMethods have done in Event manager is somewhat similar: the Event manager subscription and filters define “join points” in AOP-lingo and the subscribing services are analogous to AOP “advice”.

Regards,
sonam

Hello,
I am speaking of only on 6.0.1 by the way, so improvements in the latest are not quite yet known to me.

It is very simple to play with and get the gist of where you can go with Event Manager. For example, i have a simple handler that will parse out the pipeline (expand lists and table to structured text strings) and recursively execute on the nestedException. Each recursive invoke will chain down the exceptions and bubble back up to a final table.

I can use this to assign a general portion to special handlers. One that I have, sends an email per exception unit. So if you fail on one service, you will get a bunch of emails on the calling stack of services to the top non handling caller. Another can save the lot as one file or as a group of files.

One of its downsides in my book is that only the service name is a choice for the filter. I would have liked to of been able to somehow specifiy an Exception type. I also would have liked to of been able to choose whether the execution is synchronous or async. This way a logger could be async and a database connection or file handle closer could be synchronous. Good day.

Yemi Bedu

Hi All,

In camp 1 & 3, sonam has listed that in Catch bloch “EXIT with failure”, do we need to explicitly specify the exit step in catch block. I understand that
if the sequence of catch block is set to exit at done, that will do the purpose.
Is there any performance/resource implication of using explicit exit step in catch block, should we use or not…?
Thanks,
KK

Hello,
The exit{signal failure} will not work in a seq that has “exit on done” or “exit on success” set. I didn’t completely read sonam’s examples before (had to scroll the mini pane/ ignoring the illogical). The way sonam’s example would work is to have the typical:

seq: exit on success

  • seq: exit: on-failure
  • seq: exit: on-done

branch: switch:exception

  • seq: exp1 exit: on-failure
    – exit: signal: failure ## AN APPROPRIATE POINT FOR EXIT
  • seq: exp2
    – map
    – exit: signal: success

It is probably good to not read it literally as a line by line representation. There are other posts with more elaborate examples of setting up the try-catch-dispatch pattern. Good day.

Yemi Bedu

The exit{signal failure} will not work in a seq that has “exit on done” or “exit on success” set.

I’m puzzled about this statement – here’s the section from “GEAR 6 webMethods Developer’s Handbook” Pg 17 that seems to say that an EXIT with failure as the last step in a sequence set to exit-on-DONE is fine:


SEQUENCE1 (main sequence, set to exit on SUCCESS) 
   SEQUENCE2 (logic sequence, set to exit on FAILURE)
     Service1
     Service2
     …
   SEQUENCE3 (error sequence, set to exit on DONE or as required)    
      pub.flow:getLastError 
      EXIT ‘$flow’ and signal FAILURE

The purpose of the ‘EXIT and signal failure’ in my example would be to bubble the error up to the calling service. Would this not work?

Sonam