2
\$\begingroup\$

I am writing some code that will serve a website and I have some questions about my "architecture".

  1. Domain - I have a database and and at the lowest level I have the **Domain* *package that contains the classes that represent the tables of the database. I use Hibernate and lazy fetch for relationships.
  2. Access This package has all the classes that perform database actions on the domains. I guess this is the equivalent of DAO. I get entries using the primary key, return all the entries in a table, perform a query on it. Everything is returns as a domain class or a collection of it.
  3. Service - This packages has the class (again related to a domain class each) that has more complex logic. It uses the Access package to get domain objects and transform them to model objects where model is what I call classes that represent an equivalent domain class but without the members that have relationships on them like *ToMany which would possibly have hibernate proxies that cannot be serialised and also make the object "heavier". In the future I might write custom methods/transformation to turn those collections of domain object to something descriptive for presentation but for now I disregarded them.
  4. Model - This package has the exact same number of classes as the Domain and like I mentioned is the a representation of the domain objects to something I can use for presentations, transmit, etc. (This is like parallel to the other hierarchy not part of the order.)
  5. Servlet This packages contains all the Servlets for the websites and each servlet contains the code for something the website wants to do. It uses the service classes to get the data it wants to manipulate. The service classes will get the relevant domain objects and transform them to model objects which will be returned to the servlet classes that will perform the operations needed by the website request and then return to the website the data in JSON format.

So obviously I would like some feedback to this approach and my following thoughts.

  1. I think the service classes should have only code having to do with transforming the domain object to a model object. I am thinking of using Dozer and just add code that might be needed for something more complex that Dozer can't do (basically the in the future bit). From what I saw since my model classes are basically domain classes without the heavy stuff and the members have the same name I don't even need annotations or XML.

  2. In the Access I use as a parameter the base class of all domain classes so I can have an abstract class and implement all the common methods in there like so

    public abstract class DomainAccess<T extends Domain> {
    
        protected abstract Logger getLogger();
    
        protected DatabaseFacade db;
    
        protected Class<T> domainClass;
    
        @Inject
        public DomainAccess(DatabaseFacade databaseFacade, Class<T> domainClass) {
            this.db = databaseFacade;
            this.domainClass = domainClass;
        }
    
        @SuppressWarnings("unchecked")
        public T fetchByPrimaryKey(Object primaryKey) {
            return (T) db.find(domainClass, primaryKey);
        }
    
        // TODO This might be better to be used for complete comparison if expanded
        public boolean exists(T object) {
            return fetchByPrimaryKey(object.getPrimaryKey()) == null ? false : true;
        }
    
        public void save(T object) {
            db.save(object);
        }
    
        public void merge(T object) {
            db.merge(object);
        }
    
        public void delete(T object) {
            db.remove(object);
        }
    
        public void saveOrUpdate(T object) {
            if (exists(object)) {
                merge(object);
            } else {
                save(object);
            }
        }
    
        public void deleteByPrimaryKey(T object) throws EntityNotFoundException {
            Object primaryKey = object.getPrimaryKey();
            T objectToDelete = fetchByPrimaryKey(primaryKey);
    
            if (objectToDelete == null) {
                getLogger().debug("There was no entry found with primary key: " + primaryKey);
                throw new EntityNotFoundException("No entry was found with specified primary key [" + primaryKey + "]");
            } else {
                getLogger().debug("Deleting entry with id: " + primaryKey);
                delete(objectToDelete);
            }
        }
    
        @SuppressWarnings("unchecked")
        public List<T> getResultList(String hql, String... parameters) {
            TypedQuery<T> query = db.createTypedQuery(hql, domainClass);
            for (int i = 0; i < parameters.length; i++) {
                query.setParameter(i + 1, parameters[i]);
            }
            return query.getResultList();
        }
    
        @SuppressWarnings("unchecked")
        public T getSingleResult(String hql, String... parameters) {
            TypedQuery<T> query = db.createTypedQuery(hql, domainClass);
            for (int i = 1; i <= parameters.length; i++) {
                query.setParameter(i, parameters[i - 1]);
            }
    
            return query.getSingleResult();
        }
    }
    
  3. Similarly, I think in the Service I think I should use the Model as a parameter like this:

    public abstract class DomainService<T extends Model> {
    
        protected abstract Logger getLogger();
    
        protected final Validator validator;
    
        protected DomainService() {
            // TODO this might be needed only for insertion so instead of a class member, maybe it's better to have it as
            // a method variable?
            ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
            this.validator = factory.getValidator();
        }
    
        /**
         * Inserts an entry in the database for the object passes an an argument.
         * 
         * @param object The object representing the entry to be inserted
         * @throws ValidationException When the object doesn't pass the validation. i.e. a member value is not valid based
         */
        //TODO handle validation exception with message
        abstract public void insert(T object) throws ValidationException;
    
        /**
         * Deletes an entry from the database. A whole object of the appropriate type is passed as an argument which
         * will be used as a storage/collection of the attributes of the entry by which the deletion can occur. Different
         * implementations can use these attributes to performs filtering and collection of entries to be deleted.
         * 
         * @param object An object representing the entry to be deleted.
         * 
         * @throws EntityNotFoundException when no entry to be deleted is found
         */
        // TODO remove TransactionRequiredException, IllegalArgumentException
        abstract public void delete(T object) throws EntityNotFoundException;
    
        /**
         * Returns all the entries of the table.
         * 
         * @return a list containing objects representing all the entries in the table.
         */
        abstract public List<T> fetchAll();
    }
    

    So, in the servlet where I will have all the values for an object I will construct a member instance and then pass that down to the service which will transform it to a domain or use it to update an existing domain object (it was retrieved from the database for instance) and so on.

\$\endgroup\$
1
\$\begingroup\$

I have some superficial comments about the code itself.


Use boolean values directly. So instead of this:

return fetchByPrimaryKey(object.getPrimaryKey()) == null ? false : true;

Write like this:

return fetchByPrimaryKey(object.getPrimaryKey()) != null;

The getResultList and getSingleResult methods are confusing. Their main loops are written slightly differently, but their logic is actually the same. As such, the two methods differ only in their last line. To avoid duplicated code, it would be better to move the common part to a private helper:

private TypedQuery<T> newTypedQueryWithParams(String hql, String... parameters) {
    TypedQuery<T> query = db.createTypedQuery(hql, domainClass);
    for (int i = 0; i < parameters.length; i++) {
        query.setParameter(i + 1, parameters[i]);
    }
    return query;
}

@SuppressWarnings("unchecked")
public List<T> getResultList(String hql, String... parameters) {
    TypedQuery<T> query = newTypedQueryWithParams(hql, parameters);
    return query.getResultList();
}

@SuppressWarnings("unchecked")
public T getSingleResult(String hql, String... parameters) {
    TypedQuery<T> query = newTypedQueryWithParams(hql, parameters);
    return query.getSingleResult();
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.