Announcement Announcement Module
Collapse
No announcement yet.
Service Layer Exceptions Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • #16
    Originally posted by ramoq View Post
    So Essentially it's OK for me to do all the null checking I want, all the validation I desire on any parameters as long as I throw back valid RTE's? Whether custom or not? (Most likely custom wrapped RTE's seem most favourable in a service scenario). The NO-NO is doing all that validation and throwing back Checked Exceptions?

    Am I understanding this correctly?
    That's correct. I know I have been using a zip code class as an example a few times in other threads... Here's one version of such class from one of my old projects (very specific requirements.) It probably has more stuff than you are interested in but it demonstrates the internal basic validation and exception throwing.

    Code:
    package com.xxxxxxx.address.domain;
    
    import com.xxxxxx.common.domain.InputParsingException;
    
    import java.io.Serializable;
    import java.text.MessageFormat;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    /**
     * This class implements a US Zip Code object. An instance may be  
     * created with an empty code, or a valid non-empty code that satisfies 
     * the US ZIP format. No instances of this class may be created with
     * invalid data. A RTE will be thrown if an attempt is made to pass  
     * invalid data.
     *
     * @author constv
     */
    public class ZipCode implements Serializable {
    
        private static final String ERR_MSG_INVALID_ZIP_FORMAT =
                "Invalid Zip Code format: \"{0}\". The Zip Code must be either 5 digits, 9 digits, or 5 digits followed by a dash and 4 more digits.";
        private static final String ERR_MSG_INVALID_NULL_IN_2ARG_CONSTRUCTOR =
                "Invalid invocation of constructor ZipCode(String zip5, String zip4). The 5-digit code field may not be null.";
    
        /**
         * The regular expression that validates the format of the zip code input string. It allows the following forms of
         * input:
         * <pre>
         *   #####        - a 5-digit code
         *   #####-####   - a 9-digit code with a dash between the 5-digit code and the 4-digit extension
         *   #########    - a 9-digit code without a dash
         *   #####-       - a 5-digit code with a dash after the first 5 digits but no 4-digit extension
         * <p/>
         * </pre>
         */
        @SuppressWarnings({"HardcodedFileSeparator"})
        private static final String ZIP_REGEXP = "^\\d{5}[\\-]?(\\d{4})?$";
    
        private char[] zip5;
        private char[] zip4;
    
        /**
         * Constructs a <tt>ZipCode</tt> object with no data, which could be a valid case for addresses where no zip code is
         * specified, i.e. a zip code is an empty line.
         */
        public ZipCode() {
        }
    
        /**
         * Constructs a <tt>ZipCode</tt> object from the given input string. The constructor allows an empty string to be
         * passed. If the input string is not empty, it is validated against the acceptable zip code formats.
         *
         * @param zip string of characters for the Zip code; the valid input must be either 5 digits, or 5 digits followed
         *            by a dash and 4 more digits.
         * @throws com.imagitas.common.domain.InputParsingException
         *          if the input string does not conform to the zip code format
         */
        public ZipCode(String zip) {
            if (zip != null && zip.length() > 0) {
                parse(zip);
            }
        }
    
        /**
         * Constructs a <tt>ZipCode</tt> object from two separate fields: the 5-digit code and the 4-digit add-on, if the
         * latter is not empty. This is just a convenience constructor to save the client from performing some extra string
         * concatenation.
         *
         * @param zip5Str 5-digit zip code; may not be null.
         * @param zip4Str 4-digit add-on
         */
        public ZipCode(String zip5Str, String zip4Str) {
            if (zip5Str == null) {
                throw new InputParsingException(ERR_MSG_INVALID_NULL_IN_2ARG_CONSTRUCTOR);
            }
    
            if (zip4Str != null) {
                parse(new StringBuilder(9).append(zip5Str).append(zip4Str).toString());
            } else {
                parse(zip5Str);
            }
        }
    
        /**
         * Sets the zip code on an already constructed instance of the ZipCode class.
         *
         * @param zip zip code string
         */
        public void setZip(String zip) {
            if (zip != null && zip.length() > 0) {
                parse(zip);
            }
        }
    
        /**
         * Sets the zip5 code on an already constructed instance of the ZipCode class.
         *
         * @param zip zip code string
         */
        public void setZip5(String zip) {
            setZip(zip);
        }
    
        /**
         * Gets the Zip code as the 5-digit code.
         *
         * @return zip code as the 5-digit string
         */
        public String getZip5() {
            if (zip5 == null) {
                return "";
            } else {
                return new String(zip5);
            }
        }
    
        /**
         * Gets all the digits of the zip code (5 or 9) without a dash.
         *
         * @return 5 or 9 digits of the zip code if the zip is defined
         */
        public String getZipAllDigits() {
            return new StringBuilder(9).append(getZip5()).append(getZip4Ext()).toString();
        }
    
        /**
         * Indicates whether this zip code contains the 4-digit extension.
         *
         * @return boolean <tt>true</tt> or <tt>false</tt>
         */
        public Boolean hasZip4Ext() {
            return zip4 != null;
        }
    
        /**
         * Gets the extending 4-digit part of the zip code.
         *
         * @return zip code's 4-digit part
         */
        public String getZip4Ext() {
            if (zip4 == null) {
                return "";
            } else {
                return new String(zip4);
            }
        }
    
        /**
         * Gets the full zip code string if both the 5-digit and 4-digit parts are available; if only the stored sip code is
         * represented by 5 digits only, it is returned as a 5-digit code.
         *
         * @return zip code string
         */
        public String getFullZip() {
            if (zip4 == null) {
                return getZip5();
            } else {
                return new StringBuilder(9).append(zip5).append('-').append(zip4).toString();
            }
        }
    
        /**
         * Returns the Zip Code string.
         *
         * @return zip code string
         */
        @Override
        public String toString() {
            return getFullZip();
        }
    
        /**
         * Validates and parses the input string into the zip code fields. The input string is validated using the {@link
         * #ZIP_REGEXP} regular expression.
         *
         * @param zip zip code string
         */
        private void parse(String zip) {
            validate(zip);                     // validate against regular expression
            zip = zip.replace("-", "");        // after string was successfully validated, remove dash, if present
            this.zip5 = new char[5];           // create and populate 5-digit field
            zip.getChars(0, 5, this.zip5, 0);
            if (zip.length() == 9) {           // if 4-digit extension present, create and populate that field
                this.zip4 = new char[4];
                zip.getChars(5, 9, this.zip4, 0);
            }
        }
    
        /**
         * Validates the input string for matching the requirements for a zip code.
         *
         * @param zip string to validate
         */
        private static void validate(String zip) {
            if (!isValid(zip)) {
                throw new InputParsingException(MessageFormat.format(ERR_MSG_INVALID_ZIP_FORMAT, zip));
            }
        }
    
        /**
         * Validates the input string for matching the requirements for a zip code. This <i>utility</i> method may be used
         * to check if the input string matches any of the valid ZIP code formats without creating an instance of the
         * ZipCode class.
         *
         * @param zip string to validate
         * @return true if the input string matches a valid ZIP code format
         */
        public static boolean isValid(String zip) {
            Pattern p = Pattern.compile(ZIP_REGEXP);
            Matcher matcher = p.matcher(zip);
            return matcher.matches();
        }
    }
    Note that the class is designed not to allow invalid instances to be created. If the input is completely out-of-wack and no valid ZIP code can be constructed, a meaningful runtime exception (with detail message) will be thrown. (It will be up to the application to catch that in an appropriate place, e.g. where the ZIP population occurs. A more generic and simple way - when used in a form - is to let Spring catch it during binding and produce a MethodInvokation exception that would later be mapped to the message key, like "methodInvocation.zip" during validation. But these details may be of no interest to you now, and I had some specific requirements t implement it that way. In many cases of course, all you need is a simple string for the ZIP code + simple field validation in some AddressValidator. )

    Comment


    • #17
      - The whole time I was confused that we should do NO EXPLICIT ERROR throwing(rte or regular exception) and just let run-time errors happen as they occur. I was thinking this was the meaning of run time errors: just don't do anything and let the program break along the way.

      - Essentially I was assuming that there is no point in validating the method params bcs the runtime error will occur (NPE or whatever) when we try access the params in the services logic. Thus we're not using checked exceptions anymore Rte's will occur naturally)

      - Because when I think runtime errors I always just assumed runtime errors means anything that the jvm throws (and only the jvm). We as programmers have no business dealing with them.

      goodness, what a learning experience. thanks guys!

      Comment


      • #18
        Originally posted by ramoq View Post
        - The whole time I was confused that we should do NO EXPLICIT ERROR throwing(rte or regular exception) and just let run-time errors happen as they occur. I was thinking this was the meaning of run time errors: just don't do anything and let the program break along the way.

        - Essentially I was assuming that there is no point in validating the method params bcs the runtime error will occur (NPE or whatever) when we try access the params in the services logic. Thus we're not using checked exceptions anymore Rte's will occur naturally)

        - Because when I think runtime errors I always just assumed runtime errors means anything that the jvm throws (and only the jvm). We as programmers have no business dealing with them.
        And you are not the only one. This brilliantly illustrates my point about the overwhelming misunderstanding of error-handling in Java. When Sun introduced checked exceptions in Java, they literally announced that they had all but solved the error-handling problems. I remember reading a quote online a long ago: one of the people at Sun who pushed for checked exceptions and finally won (it's a woman, I don't remember her name) saying something to the effect of the world seeing the light again... The sad reality is, I think error handling is misunderstood and misused in Java more than in any other language. Well, thank you ma'am, whatever your name is...

        No, RTEs are not out there to blow things up. NO, no, no! They absolutely must be caught and handled, one way or the other. No exception should be left unhandled. (Just think of mission-critical applications that run on systems where human lives are involved! Should a space shuttle blow up if a NPE occurs?) Sometimes, handling may involve as little as logging and exiting gracefully - at the very least. But nothing less than that! Also, even when you think that you can swallow the exception, you must log it, at least. That information may later be extremely useful in indicating that something unusual did happen, and you eventually need to keep track of such things.

        goodness, what a learning experience. thanks guys!
        You are welcome. I hope this helps. One person at a time... Help us spread the word.
        Last edited by constv; Mar 3rd, 2009, 07:32 PM.

        Comment


        • #19
          - In regards to RTE's thrown at transaction commit times, ie. the db unique constraint is violated etc.

          - Since generally my service methods are marked with @Transactional, should I proxy these calls and catch the RTE's then wrap them and return back a valid msg to the user?

          ie.
          Code:
          public User createUser(String email, String name){
               try{
                     createUserTransaction(email, name);
              }catch(/* runtime error */){
               //throw back meaningful RTE wrapping the exception
              }
          
          }
          
          @Transactional
          private User createUserTransaction(String email, String name){
             //construct domain object, dao/persist logic
          }
          I can't see another way to catch these RTE's and return back a meaningful RTE to the client. (And even if I did an explicit check to see if the email is unique, it's not guaranteed until the transaction commits)

          Comment


          • #20
            It is not exactly so - for the most databases such kind of checks are performed at the moment of actual operation (insert/update/delete) and not on commit. In some databases (e.g. Oracle) constraints may exist in immediate (default) and deferred mode, but latter is used rather rarely.

            If use use Hibernate (or something similar) operations may seem to be deferred till transaction end, but in reality they are delayed not till commit, but till session flush, commit triggers flush, but you may as well call flush manually before leaving method.

            So in the most circumstances you are able to catch and process (e.g. wrap and rethrow) exceptions inside your method.

            In rare cases when you have to deal with deferred constraints there are several possibilities.
            • You may use some wrapper with non-transactional methods which you expose to the clients of your service and then delegate calls to the real service and then dials with exceptions (if any).
            • You may use AOP to apply after-throwing advice to your service methods. But I'm not sure how to place before transactional aspect. As far as I can remember (not 100% sure) Spring has some problems with this. You may try a search a AOP forum for appropriate discussions,
            • Depending on your application (e.g. if you are using Spring MVC and/or Spring WebServices) it may be enough to configure handler exception resolvers in an appropriate way.
            There may be other possibilities that I can not remember right now.

            Regards,
            Oleksandr


            Originally posted by ramoq View Post
            - In regards to RTE's thrown at transaction commit times, ie. the db unique constraint is violated etc.

            - Since generally my service methods are marked with @Transactional, should I proxy these calls and catch the RTE's then wrap them and return back a valid msg to the user?

            ie.
            Code:
            public User createUser(String email, String name){
                 try{
                       createUserTransaction(email, name);
                }catch(/* runtime error */){
                 //throw back meaningful RTE wrapping the exception
                }
            
            }
            
            @Transactional
            private User createUserTransaction(String email, String name){
               //construct domain object, dao/persist logic
            }
            I can't see another way to catch these RTE's and return back a meaningful RTE to the client. (And even if I did an explicit check to see if the email is unique, it's not guaranteed until the transaction commits)

            Comment


            • #21
              Originally posted by ramoq View Post
              - In regards to RTE's thrown at transaction commit times, ie. the db unique constraint is violated etc.

              - Since generally my service methods are marked with @Transactional, should I proxy these calls and catch the RTE's then wrap them and return back a valid msg to the user?

              ie.
              Code:
              public User createUser(String email, String name){
                   try{
                         createUserTransaction(email, name);
                  }catch(/* runtime error */){
                   //throw back meaningful RTE wrapping the exception
                  }
              
              }
              
              @Transactional
              private User createUserTransaction(String email, String name){
                 //construct domain object, dao/persist logic
              }
              I can't see another way to catch these RTE's and return back a meaningful RTE to the client. (And even if I did an explicit check to see if the email is unique, it's not guaranteed until the transaction commits)
              It seems to me that in this particular example you are not going to clarify anything by catching and re-throwing. Think about it. If you do nothing, just allow the RTE from the data tier (I assume you are using Spring data access support that will wrap SQLExceptions into RTEs) propagate all the way to your top-level resolver, log it and display the generic error page, your log file will contain the stack trace that will tell you just exactly what your new RTE would have told: a constraint violation has occured during a call to "createUser()". The transaction will be rolled back anyway, if you properly configure your transaction manager. An additional "clarifying" RTE is not going to add any more useful info. So, don't bother. Also, I would not bother extracting the DAO call into a private transactional method, unless there's a lot more going on in your public createUser method. Just define createUser as @Transactional.

              Now, if you do know what a constraint violation really means in this particular case, e.g. "duplicate user", then you indeed might want to translate that exception into a meaningful DuplicateUserException (if you really want to signal this condition with an exception) and use a resolver to listen to DuplicateUserException specifically and map it to a different view, if necessary. And you probably would want to do that inside the DAO, without exposing this to the service method, since your data access implementation is supposed to be abstracted from the business logic in the service. What if your data store changes - hypothetically - and you start retrieving the same data from a file system instead. No more constraint violation exceptions, but your service has the code that assumes they occur. Put DA-specific logic inside DAOs and keep it there.

              Also, you might want to have some utility class somewhere in your common area - with generic validation methods such as "ValidationUtils.notNull(Object o, String classname)", and the likes of that. You can write an aspect that you can apply to all your public service methods that would check all method arguments for not being null - before executing the method. This will not affect the code of your service methods, since the logic will be implemented once and externalized in an aspect. The aspect would throw some InvalidArgumentException with the detail message containing the name of the class whose instance was not passed (null) properly, etc.

              So, in most cases, you don't need to have any try/catches in your service or DAO methods at all - unless some 3rd party or JDK methods throw checked exceptions that you will need to catch and wrap into meaningful RTEs. In fact, most of my service and DAOs are squeaky clean, you'd be hard pressed to find a try/catch in them. And may I say so, by the time my applications are in QA, I don't miss any errors, and my log files read like a book.

              Good luck.

              Comment


              • #22
                Originally posted by constv View Post
                It seems to me that in this particular example you are not going to clarify anything by catching and re-throwing.
                ...
                Hi Constantine, all what you said is true, but you somewhat missed a point, sorry. The question essentially was "what to do with delayed exceptions?".

                There are some situations when exceptions are thrown not at the moment of the method call but in some (somewhat indeterministic as it may be changed by configuration external to your code) point later, probable long after return from your DAO methods. I have explained some such situations in my previous post.

                You may be interested to look (if you have not done it before) on chapter 10.10 of Hibernate Reference. And note that while in case of Hibernate you may force immediate flush, it may impact performance catastrophically (up to 2 orders of magnitude, i.e. 100 times).

                If it is caused by deferred (i.e. enforced only on commit) constraints in DB then you can do absolutely nothing about it (assuming that there were valid reasons to create those constraints in deferred mode - and sometimes such reasons exist).

                So the question is - what is a best solution in this case (aside resolvers, they are applicable not always) ? I'm very interested in your opinion.

                Regards,
                Oleksandr

                Comment


                • #23
                  The question essentially was "what to do with delayed exceptions?".
                  I must have misunderstood. I thought we were talking about simply handling exceptions in the service layer during the API execution, with service API being transactional - meaning that if anything fails within the demarcated transaction - in the DAO or outside the DAO but within the service method that wraps the DAO call, the transaction would be rolled back. Assuming, of course, that the database does not do any explicit commits until the framework's transaction management triggers the commit.

                  Let's see if I understand you correctly... Are you are talking about database-related errors that may happen due to whatever external factors - after the service API successfully returns and the transaction is successfully committed? Like during Hibernate's flushing that normally happens behind the scenes and performed by Hibernate at its own will unless we force it? Is this what ramoq was asking?

                  Hmm... I agree with you that forced flushing every time you execute a query is expensive and most likely would be a huge overkill. It seems to me that dealing with such conditions should - normally - be trusted to the framework itself (Hibernate, in this case) and if such abnormal condition ever happens, there's little you can do on the application/service API side. In other words, I am using Hibernate because it abstracts certain things that I supposedly don't need to worry about. If that framework all of a sudden does something out of whack and screws up the integrity of the whole system, that's the framework's malfunction. That error will eventually manifest itself as a critical database exception and should go straight to the top handler for critical/fatal errors. However, I tend to think that the likelihood of such conditions hugely depends on the quality of the data access/database design, in general. Perhaps your system should not be designed so that it heavily relies on huge volumes of potentially stale cached objects, etc. Don't you agree? Stuff like that should not be happening in well designed systems, and if it does it should be treated as a marginal critical condition and looked into from the stand point of a possibility of design tweaking rather than error handling. That's my take on it. (I know, I have seen some horrendous Hibernate implementations - usually in cases where ORM was totally inappropriate, in the first place. As you have said before, people sometimes misuse good technologies. I think ORM should be used when a very clear and straightforward mapping between your object model and database schema can be achieved without making one ridiculously complex to fit the other. But that's a different topic. )

                  Comment


                  • #24
                    Originally posted by constv View Post
                    ...
                    Let's see if I understand you correctly... Are you are talking about database-related errors that may happen due to whatever external factors - after the service API successfully returns and the transaction is successfully committed?
                    Not exactly - such error occur after service API return in the process of commit (i.e. transaction would not be successfully committed).

                    Like during Hibernate's flushing that normally happens behind the scenes and performed by Hibernate at its own will unless we force it? Is this what ramoq was asking?
                    More or less - he has not explicitly mentioned Hibernate. And Hibernate (and ORM in general) is not required to obtain such kind of behavior. You may meet it even with old plain JDBC, for example, if you have in the database something like following (Oracle):

                    Code:
                    CREATE TABLE games
                      (scores NUMBER, CONSTRAINT unq_num UNIQUE (scores)
                       INITIALLY DEFERRED DEFERRABLE);
                    Such constraint is checked not on insert/update but only on commit.
                    Hmm... I agree with you that forced flushing every time you execute a query is expensive and most likely would be a huge overkill.
                    100-fold performance degradation due to excessive flushes I have witnessed first hand.

                    It seems to me that dealing with such conditions should - normally - be trusted to the framework itself (Hibernate, in this case) and if such abnormal condition ever happens, there's little you can do on the application/service API side.
                    First of all framework may not be involved, secondly, while I rather can not recover from this condition (while it depends on business requirements) I may - and rather should - provide meaningful message to the client. DUPLICATED_KEY does not do as it may be duplicated user id, duplicated SSN, and so on. There should be some piece of code that is aware of DAO implementation details. And it is better if it is independent from the way how service API was called, e.g. handler exception resolver would not always do.

                    So, from my point of view it should be some wrapper (manual or via AOP) that calls exception resolution service, which in turn calls exception resolver built-in into DAO layer.

                    Regards,
                    Oleksandr

                    In other words, I am using Hibernate because it abstracts certain things that I supposedly don't need to worry about. If that framework all of a sudden does something out of whack and screws up the integrity of the whole system, that's the framework's malfunction. That error will eventually manifest itself as a critical database exception and should go straight to the top handler for critical/fatal errors. However, I tend to think that the likelihood of such conditions hugely depends on the quality of the data access/database design, in general. Perhaps your system should not be designed so that it heavily relies on huge volumes of potentially stale cached objects, etc. Don't you agree? Stuff like that should not be happening in well designed systems, and if it does it should be treated as a marginal critical condition and looked into from the stand point of a possibility of design tweaking rather than error handling. That's my take on it. (I know, I have seen some horrendous Hibernate implementations - usually in cases where ORM was totally inappropriate, in the first place. As you have said before, people sometimes misuse good technologies. I think ORM should be used when a very clear and straightforward mapping between your object model and database schema can be achieved without making one ridiculously complex to fit the other. But that's a different topic. )[/QUOTE]

                    Comment


                    • #25
                      Originally posted by al0 View Post
                      First of all framework may not be involved, secondly, while I rather can not recover from this condition (while it depends on business requirements) I may - and rather should - provide meaningful message to the client. DUPLICATED_KEY does not do as it may be duplicated user id, duplicated SSN, and so on. There should be some piece of code that is aware of DAO implementation details. And it is better if it is independent from the way how service API was called, e.g. handler exception resolver would not always do.

                      So, from my point of view it should be some wrapper (manual or via AOP) that calls exception resolution service, which in turn calls exception resolver built-in into DAO layer.
                      Ok, I see your point. Yes, I agree. And I have stated in one of the earlier posts that if some particular data access condition may - and should - be interpreted to assist in diagnostics, it should be done on the DAO layer. If that is specific to a particular DAO call, it may be a try/catch around the dao method, if it is more general and applies to many DAO calls, I would use an aspect. The purpose of that aspect or try/catch would be just to wrap into something meaningful and re-throw, and then still have the front-end resolver catch it.

                      Comment


                      • #26
                        Originally posted by constv View Post
                        then still have the front-end resolver catch it.
                        If that front-end exist (which is not always a case).

                        Comment


                        • #27
                          Originally posted by al0 View Post
                          If that front-end exist (which is not always a case).
                          well, d'oh! I think ramoq was originally talking about his web application, specifically... Or, I may be dreaming.

                          Comment


                          • #28
                            No, you were not dreaming - he has mentioned controller, so it is almost for sure Web application.
                            Originally posted by constv View Post
                            well, d'oh! I think ramoq was originally talking about his web application, specifically... Or, I may be dreaming.

                            Comment


                            • #29
                              I haven't fully read through your replies. so before I ask more questions (i want to read it over in detail first) i'll state some answers to yoru questions.

                              - Yes, it's a web app
                              - I'm using JPA via hibernate.
                              - No deferred constraints, i'm using MySQL.

                              Comment


                              • #30
                                - So yes, essentially I DO NOT want to recover from this error. No doubt there.

                                - However, I would like to prompt the caller of this service that the email used already exists with a EmailExistsException (RTE of course ) or something along those lines.

                                - This would then allow the caller to handle this in a specific fashion. Ie. prompt the user to re-enter the password with a specific msg. "please enter a different email, this one is in use.."

                                - I guess flush() is out of the question.

                                - However, since I was keeping my DAO layer limited to very basic CRUD operations( save(), update(), delete() findByEmail() etc). This was the reason I was opting for the hacky solution I posted. Unless you guys think this is a bad design for DAO layers (containing very basic crud operations only)

                                - Should have a createUser() method in my DAO and wrap that with a try/catch in my service? Obviously the createUser DAO call would run in it's own transaction.

                                - what would a solution look like in terms of what I listed? Avoiding aspects if possible.

                                Comment

                                Working...
                                X