Announcement Announcement Module
Collapse
No announcement yet.
Resolving i18n error message : In which layer ? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Resolving i18n error message : In which layer ?

    Hello,

    We've had some discussion here about the question :
    In which layer do we resolve error message internationalisation ?

    There are two trends in our company :
    1. we resolve error message internationalisation only when we're in the presentation layer (front end).
    2. we resolve error message when throwing the exception (i.e. service layer / front layer or even technical classes - such as security classes).

    Our design must be applied to all kinds of application :
    1. standalone web application.
    2. composite web application (which call mainframe / Eis programs or resources).
    3. web services.
    4. jms based application.
    5. batch.
    and so on...

    We've had a look at our past design and at several open source framework to know how this was resolved, and I think the two trends exist, i.e.
    . [trend 2] HibernateValidator (which can be called in all layers) resolve message i18n when the validation is executed.
    . [trend 2] Acegi resolve internally message internationalisation.
    . [trend 1] Spring Validator let resolve message i18n in front layer.

    The pro for trend 2 are :
    a - message i18n is resolved at the source. So, when the application calls an external service (distributed such as web service, Jms, ... or framework such as acegi), the application
    doesn't need to create everytime all the i18n messages since they are already taken care for.

    The cons is :
    a - User ou caller's locale must be propagated in all my app layer (perhaps this is already a best pratice ? I don't know).
    b - the message resolved in the service doesn't always corresponds to the application constraints
    (i.e. the service propagates a business error with message 'The birth date (05/03/2004) is invalid' and the application uses date format dd-mm-yyyy).
    So, in this case, the application needs to overload those i18n messages.

    Do you have an opinion about the subject or best practice to advice me ?


    Sample code from acegi (trend 2)
    Code:
    - throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        Dans acegi, message résolu dans acegi avec msg erreur ET code erreur.
        i.e.
        Dans AbstractUserDetailsAuthenticationProvider :    
        public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
    
            ...
                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            ...
        }
    Sample code from Hibernate Validator (trend 2) :
    Resolve i18n message when executing validation (we give the locale in the resourceBundle).
    Code:
        
        i.e. 
        ResourceBundle lBundle = ResourceBundle.getBundle ("messages", Locale.GERMAN);
            MyPojo a = new MyPojo();
            a.setCountry( "Australia" );
            a.setZip( "1221341234123" );
            a.setState( "Vic" );
            a.setLine1( "Karbarook Ave" );
            a.setId( 3 );
            ClassValidator<MyPojo> lValidator = new ClassValidator<MyPojo> (MyPojo.class, lBundle);
            validationMessages = lValidator.getInvalidValues( a );
    3. Spring Validator (trend 2)
    The logic is here to resolve i18n message in the front end (validating just returns an ObjectError which implements MessageSourceResolvable).

  • #2
    Sorry,

    This thread didn't had success at all.

    I just was reading Expert one on one (2003 edition), and saw :
    Separate error messages for display to users from exception code, by including an error code with exceptions. When it's time to display the exception, the code can be resolved: for example, from a properties file.
    So, Rod Johnson would (at least in 2003) take the first trend...

    Ok, but I completely disagree in the following scenarios :
    1 - a client calls a distributed service (web service, Jms, ...). If the distributed service doesn't resolve i18n message, and just send the code to the client, all clients need to keep a property file including *ALL* error codes generated by the service. It's gonna to be difficult a long and difficult integration (perhaps the client would be just happy with some default i18n messages provided by the service).
    2 - some cross APIs (like Hibernate Validator) which are executed in all layers resolve i18n messages.
    3 - if I'm in a standalone application resolving the message when the exception is generated (service layer for instance) or in presentation is the same (I need only to propagate Locale object - i.e. in a ThreadLocal).

    If someone has an opinion great. If not, I'm going to take trend 2.

    Comment


    • #3
      Separate error messages for display to users from exception code, by including an error code with exceptions. When it's time to display the exception, the code can be resolved: for example, from a properties file.
      Maybe you have to relax the statement. If you let external systems also be users, it's correct again.

      IMO like many other things error code resolution is part of a component and so it should be encapsulated in it. No "user" of a component should care about error code resolution.

      Jörg

      Comment


      • #4
        Thank you very much for your response.

        In order to better understand your point of view (I'd would like to be sure), I'll just give a sample.

        Let's say I've the following service, generating SpendingLimitViolationException :

        Code:
         public class OrderManager implements IOrderManager {
        
        public final Invoice placeOrder (int customerId, InvoiceItem[] items)
              throws NoSuchCustomerException, SpendingLimitViolationException {
        ...
           if (total > getSpendingLimit (customerId) ){                                                                            
                getSessionContext() .setRollbackOnly();
                throw new SpendingLimitViolationException (total, limit);
            }
        ..
        }
        }
        and

        Code:
         public class SpendingLimitViolationException extends Exception {
            public string code;
            public string total;
            public string limit;
        }
        I'll need an object which will be able to resolve i18n message for this exception.

        With Spring, I could have all my exceptions implement MessageSourceResolvable and use in the front controller something like the org.springframework.web.servlet.support.BindStatus of Spring MVC (or some other MessageResolver)
        to resolve all exceptions implementing MessageSourceResolvable interface.

        Ok, but when I add a distributed service layer on top of my OrderManager class, I'll have 2 choices :
        1. resolve i18n msg in this distributed layer (so I'll need to create an new class for each exception class in order to add msg i18n attribute).
        2. ask clients to resolve the i18n error codes generated by my service.

        Did I fully understand your approach or have I missed sthing ?

        I'm really gratefull for your help.

        Comment


        • #5
          Definitely in the GUI layer

          i8n should definitely be done in the front end (in your GUI, whether that is JSP, swing, whatever) and not in the service or database layers, etc. The presentation is the client's job, the service layer should not assume that it knows how the client will handle the exception - what language should it be shown in, what icon should be used, or should an error even be shown at all? These are not decisions for the server.

          For example in your code, maybe you're client doesn't show an error message when it catches a SpendingLimitViolationException, maybe when the client gets a spending limit exception it wants to redirect the user to the 'make a deposit' section so they can top up their balance. Leave this sort of decision to your client, not your service layer. Also your client knows what the user was trying to do (i.e. the context in which the error occured) and may want to taylor the error message to that.

          Of course, 90% of the time you will just want to show a error message (e.g. "You've exceeded your spending limit!"). To do this you will need a resource bundle that the client has access to.

          You seem worried about defining all these errors (you would have to define them all if you did i8n in the server, its no different for the client) and how all these error messages will be delivered to the client (is this because the file will change over time?). Typically the error messages would just be bundled into your jar as a file. If distributed delivery is a problem there's no reason why the resource bundle can't be hosted on a web site and downloaded by the client when it connects to the server, etc.

          The simplest way to do resources is via a property file but there are many more advanced options (search the web). With a property file you just end up with a key/value pair, and you have one property file for each language. All you do is load your properties then getProperty() based on your error code.

          If you are using meaningful exceptions, which I recommend and it looks like your code example is doing (i.e. NoSuchCustomerException, SpendingLimitViolationException), then you can, as a convenience, use the class of the exception to resolve the error message.

          For example if you were using simple, property based resource bundles. You wold have a resouce file with an entry like:

          com.mycompany.myapp.SpendingLimitViolation=You've spent too much!
          com.mycompany.myapp.NoSuchCustomerException=The customer you specified does not exist.

          Then you just do

          showError(properties.getProperty(SpendingLimitViol ation.class));

          You could even extract this into a nice helper class (ErrorHandler.handleError(Exception e)) that you delegate to for all exceptions and not have to worry about each individual catch.

          Otherwise put an error code on your exception as an extra parameter (I'd recommend using an enum or string instead of an int, so you can get meaningful debug info).

          e.g.

          public class NoSuchCustomerException extends Exception
          {
          public enum ErrorCode
          {
          neverExisted, deleted
          }

          private ErrorCode errorCode;

          public NoSuchCustomerException(ErrorCode errorCode)
          {
          this.errorCode = errorCode;
          }

          ...
          }

          To handle this via a generic helper class, you could make your exceptions implement a common interface (CodedException) which has a getErrorCode() method defined on it.

          That's a very rough guideline as to how to do it, hopefully it points you in the right direction. Assume Rod Johnson is on the right track with this one and put your i8n in the client.

          Comment


          • #6
            But if you use a service that uses a service that uses a service you have to know all possible error codes to resolve them in a meaningful way. Ok, it might be part of the interface to those services, but I think there are limits ...

            Jörg

            Comment


            • #7
              Thanks very much for your answers.
              I'm still working on this topic (hem, ... I've not advanced very much).
              I'll update it when we'll have taken our decision.

              I'm specially sensitive to Jorg comment about
              if you use a service that uses a service that uses a service you have to know all possible error codes to resolve them in a meaningful way. Ok, it might be part of the interface to those services, but I think there are limits
              , since we have most of our business logic running on our mainframes and when those programs return error codes, they also return error messages.
              And for many of those programs, there's not any list of code errors anymore (we just have to find thos in the COBOL code) - bad documentation....

              But I'm also receptive to java guru's advice, and they indicate to resolve message i18n in the presentation.

              Think I'm going to postpone my own decision till I'll make some more coding in order to gain a better understanding.

              Comment


              • #8
                Originally posted by gonzalad View Post
                The cons is :
                a - User ou caller's locale must be propagated in all my app layer (perhaps this is already a best pratice ? I don't know).
                Spring has a LocaleContextHolder you can access from anywhere in your application.

                Comment


                • #9
                  Even in the case of web services there is a small presentation layer, namely the conversion from Java to XML. That's where the resolution of the error message should happen. It differs from the web presentation layer in one important aspect, though: the caller isn't a human being but another application, which prefers the error message to be parseable. Defining your own Locale (e.g. xx_XX) probably helps.
                  If some other application calls the service layer directly, an exception with attributes will be more valuable to it.
                  Conclusion: Jörg's original assessment still holds true.

                  Comment


                  • #10
                    How can I change default locale of hibernate validator in swing application?

                    Any suggestions clues?

                    Comment


                    • #11
                      Maybe you have to relax the statement. If you let external systems also be users, it's correct again.

                      IMO like many other things error code resolution is part of a component and so it should be encapsulated in it. No "user" of a component should care about error code resolution.

                      Comment


                      • #12
                        I've fixed my problem. 1st I was using following code:
                        Code:
                        new ClassValidator(obj.getClass(), ResourceBundle.getBundle("DefaultValidatorMessages", new Locale("mn", "MN")));
                        And then used following code:
                        Code:
                        new ClassValidator(obj.getClass(), ResourceBundle.getBundle("org.hibernate.validator.resources.DefaultValidatorMessages", new Locale("mn", "MN")));

                        Comment

                        Working...
                        X