Eureka Code Uniformity

Purpose

This page contains uniform rules for Eureka development

Helpful files

  • Checkstyle

  • Intellij Idea

General

Sonarcloud rules are important

  • If the current version of SonarCloud does not support any functionality, use a suitable implementation
    For example, instead of using .toList() - use .collect(toList()) or .collect(toUnmodifiableList()) for an immutable list.

  • If sonar provides incorrect validation, it must be discussed with a team and suppressed with a comment

Methods

Length

Method length must be less than 25 lines in most cases, excluding the declaration line and closing brace. If implementation takes more lines - try to decomposite method/class.

Naming

  1. Each method must start with a verb in imperative form: startProcessing, doOperation, performAction, mapResources

  2. The return value is boolean: is, exists, has. However, a noun can go first if it simplifies the method name: usernameExists or existsByUsername

  3. If the method is aimed to validate and then throw a validation exception, use verbs: verify, check, assert

  4. onCreate, onUpdate are valid names for listener methods

Methods/operation usage rules

  • Use isNull() / isNotNull() methods in as a method reference in lambdas, otherwise != null or == null is more clear

Arrays.asList(1, 2, null).stream() .filter(Objects::nonNull) .collect(toSet());

A proper usage of nonNull method

if (Objects.nonNull(object)) { doAction(object); }

Use this notation

if (object != null) { doAction(object); }
  • Use .forEach method if it shortens the code to a single line, otherwise, the classic for-each cycle is the more convenient and faster

Lambda is too long, extract it to a method

Or use for-each loop

  • Use if-return instead of if-else if possible

Explanation:

  1. Negative cases must be processed firstly with immediate response

  2. There is no need to split the declaration of the variable and assigment

  • Use Optional for public methods to nullable objects, private methods can return a null value, if it will be more convenient to use in code

A valid interface method declaration

Mehtod starts with find must return Optional object

If Object is intended in response use getById and throw an EntityNotFoundException

or use repository.getReferencyById(), this method throws exception internally. In addition, declare this exception in JavaDocs.

Invalid implementation, getEntityById(id)will be executed every time, to prevent it use .orElseGet

Redundant usage of optional, use this notation instead

  • The ternary operator is good to use for the return value (can be split into 3 lines with the operator at the new line) or to declare a variable (if it fits a single line)

The ternary operation fits in one line

The ternary operation is used for return value

Ternary operation is used to declare variable

  • Process negative conditions first using if-statement, positive flow must go to the end of a method

  • Return an empty collection always if the method returns a collection

 

null must not be returned in public methods or for a methods returning a collections

Not clear that collection is empty, use emptyList(), emptySet(), emptyCollection() from Collections class

  • Use static methods to indicate that class fields are not used, maybe later they can be considered to move to the utility class

  • Use static imports whenever it is possible

Not clear what of means, use fully qualified import for List.of(), Set.of(), Map.of() methods

  • Don’t modify source data to perform an action, it can be used further and those changes can lose initial information from the event/read operation.

  • Prefer a more functional approach when the method always returns a new value

    • BeanUtils.copyProperties(), collections are copied by reference, deepCopy mode if it is required to copy collection fields too

    • The method can return modified value - if it is used for model construction (so-called with methods, or chain setters)

  • Check utility method from folio-backend-tools

Logging

Log level = TRACE

  • No need to provide logs for this level

Log level = DEBUG

  • Debug payload request from HTTP method or Kafka, use suppliers for big objects: () -> getPayload()

  • Make them be enabled using Actuator (implemented in Folio) or by providing additional log4j.properties for test resources with a disabled threshold filter.

  • Indicate if important that entity/resource/record has been created/updated/deleted.

Log level = INFO

  • Helpful operation indicators: job started/finished

  • The payload must not be provided as short as possible: an identifier or name should be enough to identify the record

Log Level = WARN

  • Errors and warnings that are not blocking the application from running, but can indicate business logic issues

Log Level = ERROR

  • Errors that are blocking the application from running

Log format and examples

Unit tests

Unit tests must satisfy F.I.R.S.T principles.

Naming

Examples using @Nested class

Examples without using @Nested class

  • Tests (creating stubs): createApplicationDescriptor() → applicationDescriptor()

  • Unit tests can be structured with a @Nestedclass for each method, however it is not mandatory

  • Unit tests must use AssertJ library for uniformity, other assertion tools are less readable, but can be faster, however, there are no problems for big services using assertj

Exception validation

Make sure that all significant exception parameters are verified.

  • Exception type

  • Exception message

  • Exception response code

  • Exception parameters (if present)

Make sure that all parameters in the exception supplier method are defined not inside this lambda, otherwise it will produce sonar cloud code smell java:S5778 Only one method invocation is expected when testing runtime exceptions

Example for exception without parameters

Example for exception with parameters

Parameterized testing

Parameterized test can be used for method with processing depending on incoming data, but always producing same response. Usually it is utility methods.

Unit tests validation

  • Mutation tests can be used to verify the quality of written unit tests

  • Parameterized tests must be used for utility classes or methods whenever possible (add example, simple guide)

    1. Property / Data-driven testing - as an additional type of validation

  • Tests must be as simple as possible, and they must be fast (there is no need to test cache expiration for 12 seconds if it can be done using smaller values (100 ms as an example)

Integration tests

  • Use API calls to verify behavior

  • Always verify positive flow using integration tests

  • All integrations with outside applications/databases performed using https://testcontainers.com/

  • Real-service injections to the method are prohibited

    • In exceptional cases, they can be injected using @Authowired ProductionComponent component notation at class level

  • Asserts must be as simple as possible

  • Verify that external data is created in outside service

    • Java HTTP client can be used to verify data in Keycloak

    • JDBC Helpers classes to verify database data performing native SQL requests, if it cannot be checked using API

    • Kong created routes can be verified using wiremock and keycloak native functionality mgr-tenant-entitlements: EntitilementRoutesIT