Spring Boot Custom Components

Spring Boot philosophy is to develop applications by composing independent, modular and highly configurable components.

Spring Boot provides smart and simple mechanisms and conventions for building such components, that could be easily applied in custom projects.

In this post, we will explore those ideas, and show how to create domain-oriented custom components the Spring Boot way.

Spring by Kazimir Malevich

What Is Spring Boot

Dependencies are consistent with each other and tested in integration.

For web applications, embedded servers (Tomcat, Jetty, Undertow) are included. Even the traditional deployment into an application container is possible, Spring Boot is self-contained as default, everything is included in the final JAR and no additional infrastructure is needed (except JDK, obviously).

In addition, Spring Boot comes with a lot of production ready features, such as loggers, metrics, auditing, tracing, and monitoring.

A simple application skeleton can be easily prototyped by Spring Initializr.

Build Systems

Gradle

plugins {
id 'org.springframework.boot' version '2.4.0'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}

Alternatively, only the dependency management could be used in isolation:

plugins {
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}
dependencyManagement {
dependencies {
dependency 'org.springframework:spring-core:5.3.0'
}
}

Maven

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
</parent>

The parent POM brings the Spring Boot plugin, dependency management and other sensible defaults. All default settings can be overwritten in your POM.

Alternatively, only dependency management can be used without the parent POM by using an import scoped dependency:

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.5.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

Features Overview

Spring Boot Application

When the annotation @SpringBootApplication is included on the main class, the application is bootstrapped, auto-configuration enabled and Spring components scan is set to the current package as root.

It is recommended to locate the application class in a root package above other classes. So the package defines the search base for the components scan.

org.example.myshop
+- MyShopApplication
+- catalog
+- delivery
+- order
...

Starters

Starters bring all necessary dependencies and configurations into the application:

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}

All starters have the core starter spring-boot-starter a direct dependency, which includes auto-configuration support, logging and YAML.

Starters are part of Spring Boot philosophy, a convenient way to include components into your application.

Auto-Configuration

When auto-configuration is enabled, components found on the classpath are automatically loaded and configured.

Most standard Spring auto-configurations are highly configurable via properties.

Application Properties

Properties from the loaded files are added into Spring environment.

Application properties form a configuration structure of a Spring Boot application with default values, which are meant to be overwritten in runtime from the environment or other external sources.

Configuration properties are grouped into trees by the context, typically prefixed by the component name and feature:

# application.ymlspring:
datasource:
url: jdbc:postgresql://db:5432/test
username: admin
password: secret

Custom Components

In the following section, we take a look at techniques for creating a modular application with independent domain-driven components. The components can be assembled into a single monolithic application or separate applications as microservices afterwards.

For each component a Spring Boot starter with auto-configuration will be created.

Assembling of components into an application is achieved simply by adding the component onto the classpath. Practically, by putting the component starter into the application dependencies.

Spring Boot components are logically defined by auto-configurations, starters are an additional mechanism to bring components into the application conveniently.

Code Structure

Every component is a self-contained business capability service, exposing multiple artifacts for API and implementation, formed by physical modules.

A typical component source code structure looks like:

delivery/		-- comp. root
+- domain/ -- domain API
+- events/ -- events API
+- jdbc/ -- JDBC impl
+- rest/ -- Restful API
+- spring-boot-starter/ -- starter
+- pom.xml
+- build.gradle
\- settings.gradle

Alternatively, auto-configurations can live in a separate module, which the starter includes as its dependency.

In a single-application scenario a separate module for a Spring Boot application is created on the root level:

myshop/
+- application/
+- catalog/
+- delivery/
+- order/
...

In a microservices scenario, each component has its own application module:

myshop/
+- catalog/
| +- application/
| +- domain/
| +- jdbc/
| +- rest/
| \- spring-boot-starter/
+- delivery/
| +- application/
| +- domain/
| +- events/
| +- jdbc/
| +- rest/
| \- spring-boot-starter/
+- order/
| +- application/
| +- domain/
| +- events/
| +- jdbc/
| +- rest/
| \- spring-boot-starter/
...

For both scenarios, the separation of concerns is the structure driver. Every module has its own unique role in the final application.

Starters bring Spring Boot auto-configuration, applications bring Spring Boot application main classes, but all other modules has no Spring Boot concerns or dependencies.

For example, even if the REST module is built upon Spring Web, only the corresponding Spring framework dependency should be included:

// rest/build.gradleimplementation 'org.springframework:spring-web'

The Spring Boot Web starter will be included into the starter module only:

// spring-boot-starter/build.gradleimplementation 'org.springframework.boot:spring-boot-starter-web'

Java Packages

All the modules of a component should share a root package. The modules could be further structured by the domain feature or a technical aspect. Same features should be included in an identical package.

This strategy enables information hiding of implementation classes using Java package accessibility. Consider an example, where and mean public and package-protected, respectively:

delivery/
+- domain/
| \- src/main/java/
| \- org.example.myshop.delivery
| \- ○DeliveryService.java
+- events/
| \- src/main/java/
| \- org.example.myshop.delivery
| \- ○DeliveryDispatched.java
+- jdbc/
| \- src/main/java/
| \- org.example.myshop.delivery.jdbc
| \- ●DeliveryServiceJdbc.java
+- rest/
| \- src/main/java/
| \- org.example.myshop.delivery.rest
| \- ●DeliveryRestController.java
\- spring-boot-starter/
\- src/main/java/
\- org.example.myshop.delivery
\- jdbc
\- ●DeliveryJdbcConfig.java
\- rest
\- ●DeliveryRestConfig.java

Classes DeliveryServiceJdbc and DeliveryRestController are in the same package as DeliveryJdbcConfig and DeliveryRestConfig, respectively. This makes them accessible to the configuration classes, which is the only one place they have to be accessible from, and hidden for the rest of the world.

This kind of protection based on the basic language features is a great asset to the overall modularity, preventing the temptation to access implementation details of a foreign component, and violate its sovereignty so.

Custom Spring Boot Starter

By convention, the name of a starter starts with the component name followed by -spring-boot-starter suffix:

org.example.myshop:delivery-spring-boot-starter

Auto-Configurations

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.example.myshop.delivery.DeliveryConfiguration

Auto-configurations must be loaded that way only. Make sure that they are defined in a specific package space and that they are never the target of component scanning.

Under the hood, auto-configuration is implemented with standard @Configuration classes. Multiple configuration classes could be composed via the @Import annotation:

@Configuration
@Import(JdbcDeliveryConfiguration.class)
class DeliveryConfiguration {
...
}

The auto-configuration creates and registers all necessary Spring beans for the particular component.

A component starter is the only place configurations should exist. Other modules serve different purposes.

Dependencies

If, for example, a component has a module with Spring Web restful controllers, the starter should contain the corresponding Spring Boot Starter for web:

implementation 'org.springframework.boot:spring-boot-starter-web'

The minimal dependency every Spring Boot starter must include is Spring Boot core starter:

implementation 'org.springframework.boot:spring-boot-starter'

The component starters are then added as dependencies into the application module:

implementation 'org.example.myshop:catalog-spring-boot-starter'
implementation 'org.example.myshop:delivery-spring-boot-starter'
implementation 'org.example.myshop:order-spring-boot-starter'
...

Configuration Properties

@ConfigurationProperties(
prefix = "myshop.delivery")
@Setter
@Getter
class DeliveryProperties {
private String cargoName;
private String dateFormat;
}

Configuration properties are meant to be a convenient way for initializing auto-configuration:

@Configuration
@EnableConfigurationProperties(
DeliveryProperties.class)
class DeliveryConfiguration {
@Bean
DeliveryService deliveryService(
DeliveryProperties properties
) {
return new DeliveryServiceImpl(
properties.getCargoName(),
properties.getDateFormat()
);
}
}

The Spring Boot application defines the configuration structure with default values:

# application.ymlmyshop:
delivery:
cargo-name: PPL
date-format: yyyy-mm-dd
order:
prefix-id: OrderID

Defaults can be overwritten in runtime, for example via environment variables:

MYSHOP_DELIVERY_CARGO_NAME=DHL

Conclusion

Auto-configurations provide a convenient mechanism for creating independent components in isolation.

Spring Boot starters contain configurations and dependencies for components and define configuration structure via configuration properties with dedicated namespaces.

A Spring Boot application assemblies the components and provides cross-cutting concerns in addition.

Example

Links

Originally published on my blog.

Software developer and occasional blogger: https://blog.ttulka.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store