Skip to content

Add example for Spring Modulith #222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Apr 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build-and-publish-antora.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches:
- master
- docs/**
paths:
- 'docs/**'
- 'antora-playbook.yml'
Expand Down
1 change: 1 addition & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ All tutorials are documented in AsciiDoc format and published as an https://anto
|link:test-rest-assured[Spring Test: Integration with RestAssured] | Implement Behaviour Driven Development with https://rest-assured.io/[RestAssured]
|link:test-slice-tests-rest[Spring Test: Implementing Slice Tests for REST application] | Dive into available options to implement tests with Spring Boot's test components
|link:web-rest-client[Spring Web: REST Clients for calling Synchronous API] | Implement REST client to perform synchronous API calls
|link:modulith[Spring Modulith: Building Modular Monolithic Applications] | Structure Spring Boot applications into well-defined modules with clear boundaries
|===
4 changes: 3 additions & 1 deletion docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
** xref:data-rest-validation.adoc[REST Validation]
* Spring GraphQL
** xref:graphql.adoc[GraphQL Server]
* Spring Modulith
** xref:modulith.adoc[Building Modular Monolithic Applications]
* jOOQ
** xref:jooq.adoc[jOOQ]
* Spring Test
Expand All @@ -26,4 +28,4 @@
** xref:test-rest-assured.adoc[Integration with RestAssured]
** xref:test-slice-tests-rest.adoc[Implementing Slice Tests for REST application]
* Spring Web
** xref:web-rest-client.adoc[REST Clients for calling Synchronous API]
** xref:web-rest-client.adoc[REST Clients for calling Synchronous API]
25 changes: 25 additions & 0 deletions docs/modules/ROOT/pages/batch-rest-repository.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
= Spring Batch: Working With REST Resources
:source-highlighter: highlight.js
Rashidi Zin <rashidi@zin.my>
2.0, September 27, 2023
:nofooter:
:icons: font
:url-quickref: https://github.com/rashidi/spring-boot-tutorials/tree/master/batch-rest-repository
Expand All @@ -22,39 +24,48 @@ consists of `ItemReader` and `ItemWriter`. We will implement all of them in link
----
@Configuration
class UserJobConfiguration {

private final JobRepository jobRepository;
private final PlatformTransactionManager transactionManager;
private final MongoOperations mongo;

UserJobConfiguration(JobRepository jobRepository, PlatformTransactionManager transactionManager, MongoOperations mongo) {
this.jobRepository = jobRepository;
this.transactionManager = transactionManager;
this.mongo = mongo;
}

@Bean
public Job userJob() throws MalformedURLException {
return new JobBuilder("userJob", jobRepository).start(step()).build();
}

private Step step() throws MalformedURLException {
return new StepBuilder("userStep", jobRepository)
.<User, User>chunk(10, transactionManager)
.reader(reader())
.writer(writer())
.build();
}

private JsonItemReader<User> reader() throws MalformedURLException {
JacksonJsonObjectReader<User> jsonObjectReader = new JacksonJsonObjectReader<>(User.class);

jsonObjectReader.setMapper(new ObjectMapper());

return new JsonItemReaderBuilder<User>()
.name("userReader")
.jsonObjectReader(jsonObjectReader)
.resource(new UrlResource("https://jsonplaceholder.typicode.com/users"))
.build();
}

private MongoItemWriter<User> writer() {
return new MongoItemWriterBuilder<User>()
.template(mongo)
.build();
}

}
----

Expand All @@ -64,15 +75,19 @@ From the code above, we can see that a `URL` form of `Resource` is assigned to `
----
@Configuration
class UserJobConfiguration {

private JsonItemReader<User> reader() throws MalformedURLException {
JacksonJsonObjectReader<User> jsonObjectReader = new JacksonJsonObjectReader<>(User.class);

jsonObjectReader.setMapper(new ObjectMapper());

return new JsonItemReaderBuilder<User>()
.name("userReader")
.jsonObjectReader(jsonObjectReader)
.resource(new UrlResource("https://jsonplaceholder.typicode.com/users"))
.build();
}

}
----

Expand All @@ -88,27 +103,37 @@ Once completed then we will verify that the database contains the expected numbe
@SpringBatchTest
@SpringBootTest(classes = { BatchTestConfiguration.class, MongoTestConfiguration.class, UserJobConfiguration.class }, webEnvironment = NONE)
class UserBatchJobTests {

@Container
@ServiceConnection
private final static MySQLContainer<?> MYSQL_CONTAINER = new MySQLContainer<>("mysql:latest")
.withInitScript("org/springframework/batch/core/schema-mysql.sql");

@Container
@ServiceConnection
private final static MongoDBContainer MONGO_DB_CONTAINER = new MongoDBContainer("mongo:latest");

@Autowired
private JobLauncherTestUtils launcher;

@Autowired
private MongoOperations mongoOperations;

@Test
@DisplayName("Given there are 10 users returned from REST Service When the job is COMPLETED Then all users should be saved to MongoDB")
void launch() {

await().atMost(ofSeconds(30)).untilAsserted(() -> {
var execution = launcher.launchJob();

assertThat(execution.getExitStatus()).isEqualTo(COMPLETED);
});

var persistedUsers = mongoOperations.findAll(User.class);

assertThat(persistedUsers).hasSize(10);
}

}
----

Expand Down
25 changes: 23 additions & 2 deletions docs/modules/ROOT/pages/batch-skip-step.adoc
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
= Spring Batch: Skip Specific Data
:source-highlighter: highlight.js
Rashidi Zin <rashidi@zin.my>
2.0, October 1, 2023
:nofooter:
:icons: font
:url-quickref: https://github.com/rashidi/spring-boot-tutorials/tree/master/batch-skip-step

Skip processing specific data through business logic in Spring Batch.


== Background

Spring Batch is a framework for batch processing – execution of a series of jobs. A job is composed of a series of steps.
Each step consists of a reader, a processor, and a writer. The reader reads data from a data source, the processor
processes the data, and the writer writes the processed data to a data source.
Expand All @@ -21,25 +23,30 @@ Our application will process data from link:{url-quickref}/src/main/resources/us
into MySQL database. Content of `users.json` is taken from link:https://jsonplaceholder.typicode.com/users[JSONPlaceholder].

== Implement Logics to Skip Data

=== Returning `null`

First approach is to return `null` from `ItemProcessor` implementation. This approach is straight forward and does not
require additional configuration.

[source,java]
----
@Configuration
class UserJobConfiguration {

private ItemProcessor<UserFile, User> processor() {
return item -> switch (item.username()) {
case "Elwyn.Skiles" -> null;
};
}

}
----

With that, when the `Job` detected that the `ItemProcessor` returns `null`, it will skip the data and continue to the next.

=== Throwing `RuntimeException`

Second approach is to throw `RuntimeException` from `ItemProcessor` implementation. This approach requires additional
configuration to be done when defining `Step`.

Expand All @@ -49,15 +56,19 @@ Implementation in `ItemProcessor` is as follows:
----
@Configuration
class UserJobConfiguration {

private ItemProcessor<UserFile, User> processor() {
return item -> switch (item.username()) {
case "Maxime_Nienow" -> throw new UsernameNotAllowedException(item.username());
};
}

static class UsernameNotAllowedException extends RuntimeException {

public UsernameNotAllowedException(String username) {
super("Username " + username + " is not allowed");
}

}
}
----
Expand All @@ -68,6 +79,7 @@ Next is to inform `Step` to skip `UsernameNotAllowedException`:
----
@Configuration
class UserJobConfiguration {

private Step step(JobRepository jobRepository, PlatformTransactionManager transactionManager, DataSource dataSource) {
return new StepBuilder("userStep", jobRepository)
.<UserFile, User>chunk(10, transactionManager)
Expand All @@ -76,14 +88,15 @@ class UserJobConfiguration {
.skipLimit(1)
.build();
}

}
----

With that, when the `Job` detected that the `ItemProcessor` throws `UsernameNotAllowedException`, it will skip the data.

Full definition of the `Job` can be found in link:{url-quickref}/src/main/java/zin/rashidi/boot/batch/user/UserJobConfiguration.java[UserJobConfiguration.java].

== Verification

We will implement integration tests to verify that our implementation is working as intended whereby `Elwyn.Skiles` and
`Maxime_Nienow` are skipped thus will not be available in the database.

Expand All @@ -104,25 +117,33 @@ We will implement integration tests to verify that our implementation is working
statements = "CREATE TABLE IF NOT EXISTS users (id BIGINT PRIMARY KEY, name text, username text)"
)
class UserBatchJobTests {

@Container
@ServiceConnection
private final static MySQLContainer<?> MYSQL_CONTAINER = new MySQLContainer<>("mysql:latest");

@Autowired
private JobLauncherTestUtils launcher;

@Autowired
private JdbcTemplate jdbc;

@Test
@DisplayName("Given the username Elwyn.Skiles and Maxime_Nienow are skipped, When job is executed, Then users are not inserted into database")
void findAll() {

await().atMost(10, SECONDS).untilAsserted(() -> {
var execution = launcher.launchJob();
assertThat(execution.getExitStatus()).isEqualTo(COMPLETED);
});

var users = jdbc.query("SELECT * FROM users", (rs, rowNum) ->
new User(rs.getLong("id"), rs.getString("name"), rs.getString("username"))
);

assertThat(users).extracting("username").doesNotContain("Elwyn.Skiles", "Maxime_Nienow");
}

}
----

Expand Down
Loading
Loading