Eureka Code Uniformity
- 1 Purpose
- 1.1 Helpful files
- 1.2 General
- 1.3 Methods
- 1.3.1 Length
- 1.3.2 Naming
- 1.3.3 Methods/operation usage rules
- 1.4 Logging
- 1.5 Unit tests
- 1.5.1 Naming
- 1.5.2 Exception validation
- 1.5.3 Parameterized testing
- 1.5.4 Unit tests validation
- 1.6 Integration tests
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
Each method must start with a verb in imperative form:
startProcessing
,doOperation
,performAction
,mapResources
The return value is boolean: is, exists, has. However, a noun can go first if it simplifies the method name:
usernameExists
orexistsByUsername
If the method is aimed to validate and then throw a validation exception, use verbs:
verify
,check
,assert
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:
| |
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 If or use | |
Invalid implementation, | |
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
| |
| |
Not clear that collection is empty, use |
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 |
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 tooThe 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
@Nested
class for each method, however it is not mandatoryUnit 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
A reflection comparison can be used to ignore specific fields for model or the models without implemented
equals
methodcontainsExactly
,containsExactrly
in any order to check collection elements of retrieving them one by onehttp://joel-costigliola.github.io/assertj/ - check other comparisons
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)
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