Announcement Announcement Module
Collapse
No announcement yet.
Service layer, transactions and reference data Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Service layer, transactions and reference data

    Hi,

    I have a business use case, where I have to allow user to delete some object only when arbitrary business conditions are met. Simply i have to retrieve an object from database, check if it is deletable with custom validator and delete it if validation succeeded. Since whole operation should be atomic it should be performed in service layer within a transaction (probably with repeatable read isolation to lock object row after reading them). It all should look like that:

    Code:
    @Service
    public class SomeServiceImpl {
    
        @Autowired
        protected SomethingDao somethingDao;
    
        @Autowired
        protected Validator validator;
    
        @Transactional(isolation=Isolation.REPEATABLE_READ)
        public Something DeleteSomethingAction(Integer id) throws ValidationException {
            Something something = somethingDao.load(id); // object must be loaded in transaction
    
            // do checking
            if(!validator.validate(something ))
                throw new ValidationException(validator.getViolatedConstraints()); // provide information to user
    
            somethingDao.delete(something);
            return something;
        }
    
    }
    
    @Controller
    public class SomeController {
    
        public String handle(ModelMap map, @PathVariable someId) {
            /* ... */
    
            try {
                Something something = someService.DeleteSomethingAction(someId);
    
                // display success
                map.add(something);
                return "success"; // view like: successfully deleted ${something.name}
            } catch(ValidationException  ex) {
    
                // display failure
                map.add( ??? ); // how to obtain reference data?
                return "failure"; // view like: failed to delete ${something.name}
            }
        }
    
    }
    The problem is within catch block. Even if deletion has failed I still want to have reference to object to pass it to the view, but I only get it when method returns normally.

    The question
    How to organize communication between presentation and service layer in that case

    Solution 1 - REJECTED
    This is how most of spring examples go. It is controller responsibility to obtain object and then call service method to delete it. This is not way to go, because it breaks transaction in two, which is bad m'kay.

    Code:
    @Controller
    public class SomeController {
    
        public String handle(ModelMap map, @PathVariable someId) {
            Something something = someService.load(someId); // transaction 1
    
            try {
                Something something = someService.DeleteSomethingAction(someId); // transaction 2
    
                /* ... */
            } catch(ValidationException  ex) {
                /* ... */
            }
        }
    
    }
    Solution 2
    Create generic Validated<Object> class, which holds both Object and verbose information about it validation. Then instead of throwing exception on deletion failure - return both object and it's errors.

    Code:
    public class Validated<Target> {
        Target object;
        Set<ConstraintViolated> violations;
    }
    
    @Service
    public class SomeServiceImpl {
    
        @Autowired
        protected SomethingDao somethingDao;
    
        @Autowired
        protected Validator validator;
    
        @Transactional(isolation=Isolation.REPEATABLE_READ)
        public Validated<Something> DeleteSomethingAction(Integer id) throws ValidationException {
            Something something = somethingDao.load(id); // object must be loaded in transaction
            Validated<Something> result = new Validated<Something>;
            result.setObject(something);
    
            // do checking
            if(validator.validate(something ))
            {
                 somethingDao.delete(something);
             }
             else {
                 result.setErrors(validator.getViolatedConstraints()); // provide information to user
             }
    
            return result;
        }
    
    }
    
    @Controller
    public class SomeController {
    
        public String handle(ModelMap map, @PathVariable someId) {
    
            Validated<Something> result = someService.DeleteSomethingAction(someId);
    
            map.add(result.getObject());
    
            if(result .getErrors() == null) {
                return "success"; // view like: successfully deleted ${something.name}
            } else {
                return "failure"; // view like: failed to delete ${something.name}
            }
        }
    
    }
    I don't really like this solution. It smells me of tight coupling between Domain Objects and validation, am i wrong?

    Solution 3
    Make service objects stateful beans responsible for one task (similarly to command pattern). When these objects gather reference data, they store it in their own properties, through which data is exposed to presentation layer.

    Code:
    @Scope("prototype")
    @Service
    public class SomeServiceImpl {
    
        @Autowired
        protected SomethingDao somethingDao;
    
        @Autowired
        protected Validator validator;
    
        protected Someting something;
    
        @Transactional(isolation=Isolation.REPEATABLE_READ)
        public void DeleteSomethingAction(Integer id) throws ValidationException {
            something = somethingDao.load(id); 
    
            // do checking
            if(!validator.validate(something))
                throw new ValidationException(validator.getViolatedConstraints()); // provide information to user
    
            somethingDao.delete(something);
            return something;
        }
    
        public Something getSomething() {
            return something;
        }
    }
    
    @Controller
    public class SomeController {
    
        public String handle(ModelMap map, @PathVariable someId) {
            String viewName = "success";
            SomeService service = serviceFactory.getSomeService();
    
            try {
                service.DeleteSomethingAction(someId);
            } catch(ValidationException  ex) {
                viewName = "failure"; 
            }
    
            map.add(service.getSomething());
            return viewName;
        }
    
    }
    Is it ok to have such stateful service objects? Most of resources I found on web state that service layer should be implemented as thin facade using stateless beans, which is opposite to presented solution.

    How do you do it in your projects?

    Regards
    Adrian
Working...
X