Project

General

Profile

Actions

Cdm Library Conventions

The following still is a draft summary of excepts - currently under discussion


Other conventions and policies are found in the CDM Library Development Resources

Persistence

The persistence layer is meant to be mainly used by experienced cdm core developers, it provides data access methods for the service layer.

In order to keep this layer clear and easy to maintain a small amount of flexible methods having many parameters is preferred over having many methods with less parameters but which are easy to use for uninitiated and less experienced cdm developers. The service layer in contrast will offer more methods to make it easy to start developing cdm applications.

Template for methods might look like:

  • getting a list of instances:

public List<T> list(Class<? extends T> clazz,... args, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)

Service Layer

The service layer provides one service class per cdm class hierarchy, this means that a service class will be responsible for delivering data related to exactly one table representing cdm instances. 2011-03-23

From my point of view we should remove all redundant methods like many of the getAllSomething() etc. as well as we should harmonize the method names like e.g. renaming like findByUuid(UUID uuid) to just load(UUID uuid) This cleaning procedure would not only affect the service layer but also the persistence layer were we have the > same redundancy.

Template for methods might look like:

  • getting and loading a cdm instance:

get(UUID uuid) and load(UUID uuid, List<String> propertyPaths)

  • saving, updating and saving or updating a single cdm instance:

save(T newInstance)@, @update(T transientInstance) and saveOrUpdate(T newOrTransientInstance)

  • saving, updating and saving or updating multiple cdm instances:

save(Set<T> newInstances)@, @update(Set<T> transientInstances) and saveOrUpdate(Set<T> newOrTransientInstances)

  • deleting a persistent cdm instance:

delete(T persistentInstance)

  • searching for instances:

findBy''Something''(.. args, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)

  • searching for instances:

public Pager<T> findBy''Something''(Class<? extends T> clazz, String queryString, MatchMode matchMode, Boolean isCaseSensitive, List<Criterion> criteria, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)

  • counting instances of type T, or subclasses:

public int count(Class<? extends T> clazz)

  • getting a (sub-)list of all instances of type T:

public List<T> list(Class<? extends T> clazz,... args, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)

  • getting a (sub-)list of instances related to a given entity:

public List<U> listRelationshipName(T t,... args, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)

  • paging a list of all instances of type T:

public Pager<T> page(Class<? extends T> clazz,... args, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths, Pager<T> pager)

  • paging a (sub-)list of instances related to a given entity:

public List<U> pageRelationshipName(T t,... args, Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, List<String> propertyPaths)

OrderHint

is an object containing two two field,s one for the propertyName, the other to define the sortOrder (type is enumeration OrderHint. SortOrder). CdmEntityDaoBase.list(Integer limit, Integer start, List orderHints) evaluates the orderHints and supports path like propertyNames which include *-to-one properties like createdBy.username or authorTeam.persistentTitleCache.

If the orderHints parameter is null the service methods will use the default orderHints provided by the static method defaultOrderHintsFor(Class<? extends CdmBase> clazz) which is "by titleCache" for all @IdentifiableEntity@s otherwise "by id".

propertyPaths:

Allows more fine grained initialization not only of the root bean identified by its uuid but also of specific paths of the object graph. The sub graph to initialize may be defined in the propertyPaths parameter as list of paths all starting at the root bean.

You can use wildcards * LOAD_2ONE_2MANY_WILDCARD and $ LOAD_2ONE_WILDCARD for initializing all *ToOne and all *ToMany relations of a bean. NOTE: A wildcard subsequently terminates its property path.

Maybe propertyPaths should be renamed into initStrategy?

MatchMode:

Specifies the mode for matching string parameters and can take the values of BEGINNING, EXACT, ANYWHERE (and perhaps should be extended to include END as well). Could be used in concert with a boolean isCaseInsensitive to allow easy switching between case-sensitive and case-insensitive matching or parameters.

There is a bit of a conflict between the persistence layer (which has a DAO per object-hierarchy) and the service layer (which has a service-per-package, sort of). Although it's nice to have a small number of services, I think it makes sense to have services for classes like FeatureTree and DescriptionElement instead of embedding them in DescriptionService, and TermVocabulary instead of embedding it in TermService, as it means (a) you can't extend ServiceBase and get all of the logic therin for free - you have to re-implement save, find, saveAll etc for these classes, and (b) you end up with awkward method names like saveFeatureTree() to distinguish from save() etc.

Services for the following objects should be created, removing the redundant methods from the relevant services

  • Collection

  • FeatureTree

  • DescriptionElement

  • TaxonTree

  • TermVocabulary

And generic methods for finding / listing / persisting the following should be removed:

  • LanguageString

  • Representation

  • FeatureNode

  • MediaRepresentation

  • MediaRepresentationPart

This would (a) remove the need for awkward method names, (b) remove redundancy in the service layer and the likelyhood that the same bug could exist in two places, (c) prevent application developers thinking that they need to manage the persistence of related objects (e.g. language strings, feature nodes) and (d) reduce the number of unique methods in the service layer by a factor of (estimate) over 50%

Unstructured Excerpts

On Pagers

Furthermore I stumbled over some inflexibility of the current Pager.

  1. It would be nice if the MAX_PAGE_LABELS could be set by a client application using service layer methods.

  2. Currently for client applications it is not possible to tell a service method using a custom pager.

To solve both issues I’d like to suggest introducing another method parameter for the service layer browse() method:

public Pager<T> page(Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, Pager<T> pager)

This would of course mean to also add an additional setRecords(List records) method to Pager (rename to IPager ?) and to modify the AbstractPagerImpl accordingly. AbstractPagerImpl would lose List records from its constructors.

Ok. That sounds like a good idea. I was customizing the pagers like this in CATE:

public Pager<T> list(Integer pageNumber, Integer pageSize) {
                Long numberOfEntities = dao.count();
                List<T> entities = dao.list(pageNumber,pageSize);
                return new StringLabelPagerImpl<T>(pageNumber, numberOfEntities,pageSize,entities) {
                        @Override
                        protected String getLabel(Integer i) {
                                T t = dao.list(i,1).get(0);
                                return t.getTitleCache();
                        }
                };
       }

(this uses the titleCache attribute to generate labels). I don't think you could do this by supplying a pager instance from outside of the method, as dao.list needs to be called in a transactional context. Perhaps we could add the following to the Pager interface:

public void generateLabels(ICdmEntityDao<T> dao);

which generates the labels, optionally using the dao to retrieve objects which lie outside the current page and thus are not in "results" (this is better, I think, than having a "setDao" method which might then expose the DAO for abuse in the controller layer once the method executes).

This would make the "browse" method look something like

public Pager<T> browse(Integer pageSize, Integer pageNumber, List<OrderHint> orderHints, Pager<T> pager) {
    Integer numberOfResults = dao.count();
    List<T> results = new ArrayList<T>();

    if(numberOfResults > 0) { // no point checking again
      Integer start = pageSize == null ? 0 : pageSize * (pageNumber - 1);
      results = dao.list(pageSize, start, orderHints);
      pager.setResults(results);
      pager.generateLabels(dao);
    }

    return pager;
}

I've been meaning to ask: Are the pageSize / pageNumber calculations supposed to be in the service layer - is everyone happy that the arguments for list(int,int) should be "limit" and "offset". I don't think this is consistently applied across the DAO's - really there should be one pattern, otherwise it will get confusing. I remember discussing this in Berlin at the beginning of the year but not the conclusion.

Updated by Andreas Müller about 1 year ago · 26 revisions