3

I have a Java EE application using standard stack of spring/hibernate/jsp and typical layers:

  • @Repository (or DAO)
  • @Service
  • @Controller

Each repository has a number of find... methods (e.g. for a BookRepository: simple findById, findByTitle, findByAuthor, more complex findMostUsed, findMostCommented etc.)

Service layer contains the business logic calling repositories as it supposed to be.

Controllers call service methods and populate ModelAndView to be used in JSP.

Of course, there are methods with some complex business logic in services. But it is annoying that there are a lot of stupid methods like this:

public List<Book> findMostUsed() {
    return repository.findMostUsed();
}
public List<Book> findMostCommented (boolean includeRating) {
    return repository.findMostCommented(includeRating);
}
...

So they are just a simple delegation (there is no business logic in these find methods - a DB query in the repository does all the selecting and grouping).

If I need to change a method in the repository the service needs to be changed as well. After 2 years of developing there was no need to add any logic to those methods - only queries and its parameters were changed.

I saw even worse design where people created Controller -> Facade -> Service -> Repository -> DAO with every layer teeming with methods like this.

What would be the better design?

Maybe to remove all those methods and transform service to be more generic (such as BookPricingService, BookRatingService, etc. instead of single BookService (which violates single responsibility principle btw)? Then the Controller will call both Service and Repository layers which is not good.

Maybe to make find methods more generic such as findByCriteria(criteria) to reduce their number. But the problem is that the queries are so different and this will end with a kind of switch-case block that picks the correct query/parameters based on the criteria type. Btw, Spring-Data also recommends one method per query with @Query annotation.

Maybe this is just the price we should pay to have the abstraction layer?

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
ike3
  • 1,760
  • 1
  • 17
  • 26

1 Answers1

2

for simple getters like that, I just inject the @Repository straight into my @Controller or where required @Service, and annotate the repository with @Transactional(propagation = Propagation.REQUIRED). That annoatation incidcates the method must be used within a transaction and will create one if not already present (but won't create a second if already within a transaction).

Service classes should be used when more than one root aggregate/entity are being manipulated - ie when there are two types of repository. As ervice bundles together methods required fro the one unit of work, so that are part of the same transaction.

I also got sick of the different patterns and dependencies.

I also use critera queries and generics to avoid lots of very similar methods, eg :

@Transactional(readOnly = true)
    public <T> List<T> getFieldLike(String fieldName, String value) {
        final Session session = sessionFactory.getCurrentSession();
        final Criteria crit = session.createCriteria(genericType).add(Restrictions.ilike(fieldName, value, MatchMode.ANYWHERE));
        return crit.list();
    }

    @Transactional(readOnly = true)
    public <T> List<T> getFieldsEq( final Map<String, Object> restrictions) {
        final Session session = sessionFactory.getCurrentSession();
        final Criteria crit = session.createCriteria(genericType);
        for (final Map.Entry<String, Object> entry : restrictions.entrySet()) {
            crit.add(Restrictions.eq(entry.getKey(), entry.getValue()));
        }
        return crit.list();
    }
Community
  • 1
  • 1
NimChimpsky
  • 46,453
  • 60
  • 198
  • 311