- avoid using
CacheProvider(cluster/cache2k) cache for objects that are or reference hibernate managed objects or detached instances of these
Transaction handling is done on the service level.
All public methods of a @Service level bean should be annotated with one of the following
@Transactional(readOnly=true): a reading TX handled by spring opens/closes, usually uses stores, might call other services@Transactional: a writing TX handled by spring opens/closes, usually uses stores, might call other services@IndirectTransactional, a TX happens but not via spring, usually using programmatic TX via JDBC@NonTransactional, there is no TX, just computation, maybe cache access and alike
Note
There are a few exceptions where @Transactional is used on the store level.
In some special circumstances this is needed to avoid a otherwise taskless intermediate service level.
Don't get inspired by these ;)
Warning
OSIV (Open Session In View) is currently active in the application but is an anti-pattern that causes database connections to be held for the entire HTTP request duration, leading to connection pool exhaustion and performance issues. OSIV will be removed entirely in the future.
Do not rely on OSIV. Instead:
- use explicit transaction management with
@Transactional - add your endpoint patterns to the OSIV exclude filter in
ConditionalOpenEntityManagerInViewFilterto prevent unnecessary database connection hold times - controllers inheriting from the abstract CRUD controller family cannot currently be excluded from OSIV as field filtering relies on OSIV, but this will be addressed in the future
Limit use of lombok to the following annotations:
@AllArgsConstructor,@NoArgsConstructor,@RequiredArgsConstructor(preferrred)@Value+@Withor@Dataor@ToString+@EqualsAndHashCode+@Getter+@Setter@Builder+Builder.@Default+@Singular@Accessors@Slf4j
Caution
@ToString and @EqualsAndHashCode require to think about the fields that should
be included or excluded. Use the @Include and @Exclude annotations.
The preferred default for objects which should not include all fields is to use
explicit inclusion.
Important
The codebase contains a few more annotations in a few places.
These should be removed/replaced. Specifically @SneakyThrows should not be used!
- use lombok annotations where possible, prefer
@RequiredArgsConstructorand mark fieldsfinal - use
records over lombok where possible - spring injected contstructors do not need nullability annotations
- never return
null(exception: private methods) - only accept
nullas parameter if that means using a default behaviour of sorts - use
@Nonnulland@CheckForNullininterfaces (and where it seems benefitial) - use immutable value types as much as possible
- assume objects passed into a public method are immutable
- return empty collections, not
null - assume collections are immutable unless they have been created within the scope (method, file)
- avoid "nested" streams, a stream should be a chain of operations on the same level
- use JDK collections only
- use
List.of,Set.of,Map.of,List.copyOf,Set.copyOf,Map.copyOfwhere possible - use stream
toList()when possible (allowsnull)
Caution
List.of, Set.of, Map.of, List.copyOf, Set.copyOf, Map.copyOf and alike do not allow
null as a value nor calling methods like contains with null.
Before using them consider if null is a possibility.
Note also that Set.of does not allow duplicates in the source collection,
Map.of does not preseve order.
- provide operations on simple types
- operations that require complex types should be located in a service
- should not accept persisted objects (exception: test helpers)
- should not accept spring beans (exception: test helpers)
- document
interfaces with javadoc - don't repeat facts the code already communicates
- make use of
{@link}and{@code} - be brief
- provide notes on background, intentions, goals, considerations
Java
- a bean annotated
@Serviceshould have theServicesuffix - a bean annotated
@Repositiryshould have theStoresuffix - a bean annotated
@Configurationshould have theConfigsuffix - a dedicated record-like class used for an endpoint's request body should have the
Requestsuffix - a dedicated record-like class used for an endpoint's response body should have the
Responsesuffix - a dedicated record-like class used for an endpoint's parameters should have
Paramssuffix - a test class should have the
Testsuffix - an abstract text class should have the
TestBasesuffix - a
getstore or service method should never returnnull, but throw an exception if no result exists - a
findstore or service method should return anOptional<X>if no result exists - use
ofandcopyOffor factory methods where suitable, e.g.UID.of
SQL
- SQL keywords use upper or lower consitently in a statement (teams can chose to only use upper or lower)
- SQL table and column names are not quoted (unless required to avoid keyword collision)
- a single column index:
CREATE INDEX IF NOT EXISTS in_{table}_{column} ON {table} ({column});
- keep controller code minimal and about presentation concerns
- use non-persisted "simple to construct" types as (service) inputs
- do not mutate method inputs, create new immutable values or accept references and load the mutated object internally
- make inputs either be data or state, never both
- keep state internal as much as possible
- services fail with
org.hisp.dhis.feedback.*exceptions - use
ErrorCodes to specify details of an exception
- use one media type per method
- use one unique path per method
- avoid mapping based on headers and parameters (and ideally even media types)
- use dedicated parameter objects for multile and/or reoccuring paramters
- use default values in parameter objects where possible
- use
204 NO_CONTENTresponse status where possible - use
@OpenApiannotations to add details that cannot be analysed - use
UIDoverStringfor IDs - use
enums overStringfor enums - use
@RequiresAuthoritywhen an endpoint always needs a special authority - use
PropertyEditors to add custom type parameters