Modern Java with Spring

2022 03 02 head

Spring and Spring Boot frameworks are the standard for enterprise Java applications.

Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can run.

They take an opinionated view of the Spring platform and third-party libraries, so you can get started with minimum fuss.

Most Spring Boot applications need minimal Spring configuration.

Minimize Boilerplate Code

One of the key reasons for the success of Spring is the approach of convention over configuration. You want to minimize boilerplate code and never have to write XML configuration files.

A lot of approaches, such as DTO force developers to write useless boilerplate code. Try to avoid these cumbersome tasks.

Use Lombok to create Java bean accessors methods and provide a builder to create instances. Always consider using modern constructs such as record and sealed class to improve the legibility of your design.

Transform your objects with MapStruct library or similar ones. Better minimize the usage of data transfer objects DTO.

Deploy as an executable application. You do not need an application server.

Package the whole application in a docker image for integration and testing activities.

Lightweight Database

Your services need to store persistent information in a database. Spring framework promotes an SQL database and JPA as object mapper.

A lightweight approach simplifies local development and speeds up the integration testing through your CI/CD pipeline.

Our current approach is:

  • use HyperSQL embedded database for local development and continuous integration pipeline. No need to install database software and fast turnaround during development.

  • isolate domain persistent data in separate databases or schemas.

  • control your persistent design using the Spring schema.sql and data.sql description files.

  • use views for read-only complex models. Always implement business logic in Java. Prohibit integrity constraints between schemas to respect the principles of domain-driven design.

  • use Flyway to update database schema and migrate persistent information into the new database schema.

  • use Spring mechanisms to load technical test data into the database before running automated integration tests. Professional software development requires the creation and maintenance of a technical test database.

Define a schema per domain to isolate your domain model in the database. Use the Spring files schema.sql to define a clean database model. Do not define any integrity rules between schemas to ensure modularity of your bounded domains down to the persistence layer.

JPA supports schemas through a standard annotation.

import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name = "Appointments", schema = "Calendar") (1)
public class Appointments {}
1 The name field defines the name of the table for all appointments, the schema field defines the schema containing the table.

Views for read-only objects Business logic should be in services.

Fluent Getter and Setter

Spring Persistence and JPA extend persistence to support Java abstractions such as enumeration sets

Use record construct for immutable entities

Configure Jackson

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@JsonIgnoreProperties({"hibernateLazyInitializer"})
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class Appointments {}

Configure lombok through lombok.config configuration file at the root of your project.

lombok.accessors.chain=false (1)
lombok.accessors.fluent=true (2)
config.stopBubbling = true
1 traditional accessors without a chaining feature. It is fully compatible with the approach provided by records.
2 discard legacy get and set verbs for getters and setters

Libraries such as Lombok, Jackson, JOOQ, Hibernate, MapStruct, Spring, Spring Boot have support for abstractions using the fluent approach.

Fluent getters are standard with the official Java record feature.

Eliminate DTO

JSON objects are your data transfer objects. JSON schema defines the data model and validates it.

  • Use annotation to remove AOT artifacts generated through JPA enhancements.

  • Use annotation to coerce Jackson to not request old fashion property getters and setters.

  • Configure Jackson to support records.

Use plain old Java objects POJO to exchange information between layers inside a Java application.

If you have to return complex aggregated objects for a persistence store, use views, persistent layer queries to populate read-only objects. Java provides the record construct for such situations. The need to define frequent queries to generate aggregate views is a smell that your design has flaws.

Schedulers in Spring

Scheduled jobs are easy to define and use with Spring.

Please do not use homebrew solutions or cron jobs. The Spring scheduler component is powerful enough for most of the scenarios. It builds up on the features of ScheduledExecutorService provided in the standard Java API.

@Slf4j
@Service
@Transactional
public class AppointmentScheduler {
    private final AppointmentService service;

    public AppointmentScheduler(AppointmentService service) {
        this.service = service;
    }

    @Scheduled(fixedDelay = 1000)
    public void sendAppointmentRatingEmails() {
        log.debug("Scheduled task to send appointment rating emails {}", LocalDateTime.now());
        service.sendRatingPendingEmails();
    }
}

User Interface

Use Vaadin for internal applications and B2B applications. Vaadin is a good approach up to a few thousand active users. Vaadin is a Java solution based on the same technology stack used in Spring. Support libraries and tutorials are provided by Vaadin to integrate the Spring framework.

Use Thymeleaf and Bootstrap for B2C applications. These frameworks introduce new technologies. Developers shall be trained to avoid bad solutions.

Use AngularJS or Vue.js if you have a lot of budget. These frameworks introduced a technology stack based on JavaScript or ideally on Typescript. New versions of the Typescript language and of the user interface framework are released every few months. Experience shows that a major rework of the source code and design decisions is necessary at least every eighteen months.

Master Advanced JPA Concepts

We had to find ways to support standard API classes in the Spring and JPA worlds. We had an enumeration set property we needed to persist.

@Data
@Entity
@Table(name = "Ratings", schema = "Calendar")
@JsonIgnoreProperties({"hibernateLazyInitializer"})
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class Rating implements Serializable {
    @Converter
    public static class RatingTagsConverter extends EnumSetConverter<RatingTags> {
        @Override
        protected Class<RatingTags> clazz() {
            return RatingTags.class;
        }
    }

    @Column(name = "tags")
    @Convert(converter = RatingTagsConverter.class)
    private EnumSet<RatingTags> tags;
}

The generic converter for the regular enumeration set Java class is:

/**
* Provides an enumeration set converter to enable storing and retrieving enumeration sets for the persistent store.
*
* @param <T> Enumeration type of the set
*/
@Converter
public abstract class EnumSetConverter<T extends Enum<T>> implements AttributeConverter<EnumSet<T>, String> {
private final static String SEPARATOR = ",";

    @Override
    public String convertToDatabaseColumn(EnumSet<T> set) {
        return set.stream().map(Enum::name).collect(Collectors.joining(SEPARATOR));
    }

    @Override
    public EnumSet<T> convertToEntityAttribute(String values) {
        return Strings.isBlank(values) ?
                EnumSet.noneOf(clazz()) :
                Arrays.stream(values.split(SEPARATOR)).map(o -> Enum.valueOf(clazz(), o.trim()))
                .collect(Collectors.toCollection(() -> EnumSet.noneOf(clazz())));
    }

    /**
     * Return the class of the enumeration type stored in the enumeration set.
     * The method is necessary due to type erasure in Java generics.
     *
     * @return class of the enumeration type
     */
    protected abstract Class<T> clazz();
}

Different approaches found on Stackoverflow or Spring blogs did not work. I was stunned by the amount of code that I needed to write to persist a standard Java API collection class.

Tips and Tricks

  • Use the Spring mechanisms to streamline unit and integration tests. Please, write unit and integration tests.

  • Train all the developers in the Spring framework [1]. The organization shall encourage certification in key technologies used in their mission-critical applications. You need at least one professional Spring developer to smooth the learning curve.

  • Use the latest Java version. Motivate your experts and allow them to work with current environments and libraries. Professional developers do not like to work with obsolete tools.

  • Use IntelliJ IDEA as an integrated development environment. The environment increases the productivity of your development teams.