Page 5 of 6 FirstFirst ... 3456 LastLast
Results 41 to 50 of 55

Thread: Service Layer Exceptions

  1. #41
    Join Date
    Aug 2006
    Location
    Now Germany, previously Ukraine
    Posts
    1,546

    Default

    Ok,

    1.
    With most databases insert (As well as other DML operations) via plain JDBC (it does not matter directly or by means of a stored procedure) would cause immediate exception in case of constraint violation. Only few databases (AFAIK) support deferred constraint and even then they are user relatively rarely (there are only a few compelling reasons to use them). So, if you have control over the database schema you practically always may ensure fail-fast behavior.

    2.
    Flush() concept is rather independent of data access implementation. It only assumes that is supports some kind of synchronization point at which all pending changes (if any) are propagated to the permanent storage and that this point may be called independently before commit. This assumption is rather sensible and so such method may be exposed by DAO layer. But even flush() does not protect against deferred constraints.

    3. So I see only one generic solution - which both of us have already proposed - aspects with after-throwing advice that catches all exceptions thrown in the service layer and call exception resolver from dao layer (directly or via exception resolution service) to translate them into something more sensible.

    But such solution requires rather a lot of efforts and in most cases I use flush in a service layer.

    Regards,
    Oleksandr

    Quote Originally Posted by constv View Post
    Yeah, it was not really relevant because it would still assume that the commit would have already occurred by the time the second thread attempts to insert, unless we put some crafty stuff inside the procedure, which would make it worse.

    Interesting... It's not easy to come up with an elegant architecturally sound solution for this, it seems. The only way to actually capture such racing conflict is upon the end of the service transaction - on the service method. However, that means we may have to make an assumption about the data access/data source implementation - in the service. Unless, as you have suggested, we put some "finalizing" method on the DAO interface that would not really expose what it does but just attempt to catch such situation and throw an appropriate exception. Neither of the approaches sits well with me. I wonder what other people think.

    I tend to think that in cases like that it may be reasonable to consider a sensible compromise. Not that I like this very much myself, but consider this... We do just a basic sanity check first - look up an existing user. If not found, proceed with an insert attempt. If a constraint violation exception occurs - specifically, perhaps, allow the user to retry once? (Ughhh...) The second time around, the sanity check should give a clear indication that the user already exists. What do you think? (In most cases, such stuff will not be needed, only in specific cases like ramoq's.) The ugly part about this, of course, is that the first time around the less clear message would still have to be shown to the user...

  2. #42
    Join Date
    Nov 2006
    Location
    Boston, MA
    Posts
    303

    Default

    Ok, I guess this thing really got to me... Couldn't stop thinking about the solution that would be more or less acceptable. Let me know what you think guys...

    Service:
    Code:
    public class UserServiceImpl implements UserService {
        
        private UserDao dao;          // reference to the data access object used by the service
        
    
        /**
         * Sets the reference to the DAO implementation for this service.
         * @param theDao dao instance
         */
        @Required
        public void setDao(UserDao theDao) {
            dao = theDao;
        }
        
    
        /**
         * Creates a new user from the given User object and returns the updated object for the new user.
         * @param theDao dao instance
         * @return updated user object (perhaps, with the new unique ID generated, or/and additional status info, etc.)
         */
        @Transactional
        public User createUser(User user) {
           // any business logic (if needed)
           try { 
                return dao.insertNewUser(user); // or, perform add'l logic and then return
           } catch (Throwable t) {
                String userEmailAddress = user.getEmailAddress();
                if (dao.lookupExistingUser(userEmailAddress) != null) {
                     throw new DuplicateUserException("A user with email address " + userEmailAddress  + " already exists"); 
                } else {
                     // wrapping here is actually redundant; you may just re-throw the original since the svc specific exception only states the obvious.
                     throw new UserServiceException("Failed to create user for username/email address: " + userEmailAddress, t); 
                }
           }
    
        }
         
        ... other methods here
    
    }
    As you see, we are catching any exception in the createUser service method without the necessity to know anything about the data tier implementation. If something goes wrong, since we are required to inform the user of an existing duplicate, if exists, we check whether the error was caused by a duplicate indeed. If yes, throw a specific Duplicate exception. Otherwise, just re-throw the original (or wrap it inside the Service specific exception, which I think is actually redundant.) No exception handling at all on the DAO side! No exposing the data tier detail to the service. Seems simple and answers the use case. Wonder what you guys think?

    Thanks,
    Constantine

  3. #43
    Join Date
    Aug 2006
    Location
    Now Germany, previously Ukraine
    Posts
    1,546

    Default

    Sorry, this solultion does not solve original problem - deferred exception.

    It may be thrown not inside DAO method (unless it explicitly calls flush), but only on commit - i.e. after return from service method. So service method has no chances to catch it (unless explicitly flushes pending changes if any).

    There is one more (smaller) problem tied to this solution - it is highly inadvisable to catch Throwable - and not rethrown immediately in a case of Error. Error's Javadoc states not without a reason

    Code:
    An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.
    Regards,
    Oleksandr

    Quote Originally Posted by constv View Post
    Ok, I guess this thing really got to me... Couldn't stop thinking about the solution that would be more or less acceptable. Let me know what you think guys...

    Service:
    Code:
    public class UserServiceImpl implements UserService {
        
        private UserDao dao;          // reference to the data access object used by the service
        
    
        /**
         * Sets the reference to the DAO implementation for this service.
         * @param theDao dao instance
         */
        @Required
        public void setDao(UserDao theDao) {
            dao = theDao;
        }
        
    
        /**
         * Creates a new user from the given User object and returns the updated object for the new user.
         * @param theDao dao instance
         * @return updated user object (perhaps, with the new unique ID generated, or/and additional status info, etc.)
         */
        @Transactional
        public User createUser(User user) {
           // any business logic (if needed)
           try { 
                return dao.insertNewUser(user); // or, perform add'l logic and then return
           } catch (Throwable t) {
                String userEmailAddress = user.getEmailAddress();
                if (dao.lookupExistingUser(userEmailAddress) != null) {
                     throw new DuplicateUserException("A user with email address " + userEmailAddress  + " already exists"); 
                } else {
                     // wrapping here is actually redundant; you may just re-throw the original since the svc specific exception only states the obvious.
                     throw new UserServiceException("Failed to create user for username/email address: " + userEmailAddress, t); 
                }
           }
    
        }
         
        ... other methods here
    
    }
    As you see, we are catching any exception in the createUser service method without the necessity to know anything about the data tier implementation. If something goes wrong, since we are required to inform the user of an existing duplicate, if exists, we check whether the error was caused by a duplicate indeed. If yes, throw a specific Duplicate exception. Otherwise, just re-throw the original (or wrap it inside the Service specific exception, which I think is actually redundant.) No exception handling at all on the DAO side! No exposing the data tier detail to the service. Seems simple and answers the use case. Wonder what you guys think?

    Thanks,
    Constantine

  4. #44
    Join Date
    Nov 2006
    Location
    Boston, MA
    Posts
    303

    Default

    Sorry, this solultion does not solve original problem - deferred exception.

    It may be thrown not inside DAO method (unless it explicitly calls flush), but only on commit - i.e. after return from service method. So service method has no chances to catch it (unless explicitly flushes pending changes if any).
    Oops, you are absolutely correct, my bad. Any such wrapping should, of course, be around the transactional method itself. So, it should be done either via an aspect - if it is applicable to more than one case, or simply on the client around the call to the service. In either case, it answers the original question: very little explicit error handling (try/catches) should normally be done on the service and DAO methods. It is ultimately up to the client to take care of its own requirements, like in ramoq's case. Or, as you had suggested before in this thread - as a not preferred solution, the transaction could be extracted into a private method on the service, but, I agree, I would rather see the public API being explicitly transactional.

  5. #45
    Join Date
    Mar 2009
    Location
    Brazil
    Posts
    25

    Default

    constv, thanks for the references on the ce and rte subject. On my last project I started to think about not using ce (suggested by one developer) and now I am convinced that I will not use it anymore. We had some bad experiences working on legacy projects with thousands of CE and it was a nightmare, the amount of lost information about the errors was insane.
    Regards
    Juliano

  6. #46
    Join Date
    Nov 2005
    Location
    Stockholm, Sweden
    Posts
    54

    Default

    Quote Originally Posted by al0 View Post
    Sorry, this solultion does not solve original problem - deferred exception.

    It may be thrown not inside DAO method (unless it explicitly calls flush), but only on commit - i.e. after return from service method. So service method has no chances to catch it (unless explicitly flushes pending changes if any).

    There is one more (smaller) problem tied to this solution - it is highly inadvisable to catch Throwable - and not rethrown immediately in a case of Error. Error's Javadoc states not without a reason

    Code:
    An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.
    Regards,
    Oleksandr

    Hmm, I don't like this stuff with deffered exceptions / triggers and such stuff, keep it simple and do it like the last sample posted - looks like what I've done since long time ago.

    Also in serveral books and tutorials both about EJB3/JPA and hibernate this seems to be what's recommended.

    /S
    --
    Consultant/Contractor
    Stockholm, Sweden

  7. #47
    Join Date
    Aug 2006
    Location
    Now Germany, previously Ukraine
    Posts
    1,546

    Default

    You are free to dislike deferred exception, but, unfortunately for you, they are part of the JPA specification (chapter 3.1.1 EntityManager Interface);
    here is excerpt from persist() Javadoc:
    * Make an instance managed and persistent.
    * @param entity
    * @throws EntityExistsException if the entity already exists.
    * (The EntityExistsException may be thrown when the persist
    * operation is invoked, or the EntityExistsException or
    * another PersistenceException may be thrown at flush or
    * commit time.)
    * @throws IllegalArgumentException if not an entity
    Put attention to the part in bold.


    Quote Originally Posted by SigmundL View Post
    Hmm, I don't like this stuff with deffered exceptions / triggers and such stuff, keep it simple and do it like the last sample posted - looks like what I've done since long time ago.

    Also in serveral books and tutorials both about EJB3/JPA and hibernate this seems to be what's recommended.

    /S

  8. #48
    Join Date
    Nov 2005
    Location
    Stockholm, Sweden
    Posts
    54

    Default

    Quote Originally Posted by al0 View Post
    You are free to dislike deferred exception, but, unfortunately for you, they are part of the JPA specification (chapter 3.1.1 EntityManager Interface);
    here is excerpt from persist() Javadoc:


    Put attention to the part in bold.
    Sure, it gets thrown when the transaction commits, in our case at the end of the service method annotated by @Transactional.

    Clarification: I hate being troubled by deferred exceptions ;-)

    /S
    --
    Consultant/Contractor
    Stockholm, Sweden

  9. #49
    Join Date
    Aug 2006
    Location
    Now Germany, previously Ukraine
    Posts
    1,546

    Default

    Quote Originally Posted by SigmundL View Post
    Sure, it gets thrown when the transaction commits, in our case at the end of the service method annotated by @Transactional.
    It is not exactly so - it may be thrown on any persistence operation between operation that causes this exception and commit (both ends inclusive). It is sole discretion of implementation when to throw it.

    Clarification: I hate being troubled by deferred exceptions ;-)

    /S
    Not only you, but there were good reasons (partially explained in this very thread) to formulate specification in such a way.

    Regards,
    Oleksandr

  10. #50

    Default

    Sorry, I realize I am late to the game on this one, but I was wondering about a little bit different approach. When you have a service that could possibly be exposed to non-java clients as a SOAP based or REST based web service, is it a good approach to include exception conditions the return type? I think that SOAP uses faults, but what about REST based services? So in the case of createUser(), maybe I am returning a CreateUserResponse object which has the created User on success (+ maybe other contextual data), but also contains the error message(s) on failure. The messages could be broken out into system vs. business errors. I ask the question because I have seen a few public web services take this approach. Any thoughts on that approach?

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •