Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Table of Contents

Purpose

This page contains uniform rules for Eureka development

Helpful files

  • Checkstyle

    View file
    nameeureka_checkstyle (1).xml

  • Intellij Idea

    View file
    nameeureka_codestyle (1).xml

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 orexistsByUsername

  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

...

  • 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

...

Code Block
"{message}: {parameter1} = {value1}, {parameter2} = {value2}"

// examples
log.info("Operation execution finished: id = {}", UUID.randomId())

// there is no need to manyally specify tenant name, because it will be populated by Folio logger extension from values from FolioExecutionContext
log.info("Tenant is initialized")

// if serialization of value is not a straight-forward, use a consumer notation
log.debug("Value is created: value = {}", () -> asJsonString(someValue))

Unit tests

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

Naming

Code Block
languagetext
{methodName}_{returnType:positive|negative}_{condition:inputValueIsNull}

...

  • 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

    • A reflection comparison can be used to ignore specific fields for model or the models without implemented equals method

    • containsExactly, containsExactrly in any order to check collection elements of retrieving them one by one

    • http://joel-costigliola.github.io/assertj/ - check other comparisons

Exception validation

Make sure that all significant exception parameters are verified.

...

Code Block
languagejava
@Test
void validateEntity_positive() {
  var expectedParameters = List.of(
    new Parameter().key("request.key").value("must not be null");
  );
  
  assertThatThrownBy(() -> service.validateEntity(request))
    .isInstanceOf(RequestValidationException.class)
    .hasMessage("Found validation errors in request")
    .satisfies(err -> assertThat(((RequestValidationException) err).getErrorParameters()).isEqualTo(expectedParams));
}

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.

Code Block
languagejava
  @Nested
  @DisplayName("existsById")
      class ExistsById {
  
    private static final UUID ENTITY_ID = UUID.randomId();

    @ParameterizedTest
    @CsvSource({"true", "false"})
    void positive(boolean entityExistsResult) {
      when(repository.existsById(ENTITY_ID)).thenReturn(entityExistsResult);
      var result = service.existsById(ENTITY_ID);
      assertThat(result).isEqualTo(entityExistsResult);
    }
  }

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

    Code Block
    languagejava
    mockMvc.perform(get("/entities")
        .header(TENANT, TENANT_ID)
      .andExpect(status().isOk())
      .andExpect(content().json(asJsonString(readTemplate("/json/responses/entity.json")), true)));
    Code Block
    languagejava
    mockMvc.perform(post("/entities")
        .content(capabilityToCreateAsJson)
        .header(TENANT, TENANT_ID)
        .contentType(APPLICATION_JSON))
      .andExpect(status().isCreated())
      .andExpect(content().json(asJsonString(readTemplate("/json/responses/entity.json"))))
      .andExpect(jsonPath("$.id").value(notNullValue()))
      .andExpect(jsonPath("$.metadata.createdBy").value(equalTo(USER_ID)))
      .andExpect(jsonPath("$.metadata.createdDate").value(notNullValue()))
      .andExpect(jsonPath("$.metadata.modifiedBy").doesNotExist())
      .andExpect(jsonPath("$.metadata.modifiedDate").doesNotExist());
  • 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