Overview

Creating Spring Boot application seems to be easy but have you ever thought more thoroughly about performance? I mean not only the performance of the application when it works but the performance of startup and test execution as well.

Some time ago I wrote a post about creating an application with Spring Boot in a classic approach.
In today’s post, I present an approach to software design that improves all three aforementioned areas. After reading this post you will start thinking more about software design and probably you will start planning it more carefully.

Goals

My goal is showing how you can speed-up your Spring Boot application written in a classic way with few simple steps. We start by improving the code that you need to run before running an entire application. What is it? Yes, of course, I mean about tests.

For needs of this post I created backend service which realizes simple functions of board for event storming sessions.

Test execution

Every advocate of TDD approach will say that we have to write a lot of tests before writing concrete business logic remembering the test pyramid but what about the performance of this test. Many programmers don’t like running tests during coding because sometimes it just takes too much time. If tests run more than 10 seconds they opening a browser to check what’s new on facebook and after 10 minutes of scrolling they continue working on the application. What a waste of time! It would be good when tests would run as fast as possible without the need for waiting.

One thing worth to mention is that having a lot of unit tests isn’t such colorful as you may think. Maybe your code is 80% covered by tests and almost every method is tested you still need to write integration tests that check how pieces of the application are working together. Sound like a dream project! Yeah! But wait a minute… what about refactoring? What happens when a business requirement will change, probably you have to adapt not only business logic but also plenty of tests. Hmm… maybe it would be better to have only integration tests to test business cases?

From my perspective, this conclusion is much better than the idea of writing tons of unit tests. Commonly known integration tests in Spring Boot application are a little bit too slow because they starting spring context. So how to improve this part of the software? The answer is simple – separate business logic from technical code. DDD approach helps to design the application in a way that helps to organize code into easy-testable modules that use other technical modules such as database repository or any external services.

My first advice: Just f*ck some Spring Boot annotations!

When you split the application structure into domain and infrastructure packages, immediately get rid of all @Service and @Component annotations from the domain part and start using the old good new keyword.

Spring Boot framework uses a lot of reflection, which is bad itself because it’s slow. The second disadvantage in this is that Spring to do this needs to be run. Knowing how Spring does this, you can do the same. You can construct the same application, as Spring Boot does, on your own. Benefit from this is that testing this code won’t enforce starting application context during tests.

Instead of creating services with Spring Boot annotation, you can do it just by instantiating it with the “new” keyword. @Bean annotation will register sole entry point to our module – BoardFacade.

 

@Configuration
class BoardConfiguration {

    @Bean
    BoardFacade boardFacade(DSLContext dsl){
        AggregateService aggregateService = new AggregateService(dsl);
        StickyNoteService stickyNoteService = new StickyNoteService(dsl);
        BoardService boardService = new BoardService(aggregateService, stickyNoteService, dsl);
        return new BoardFacade(boardService);
    }
}

 

Project structure

The project structure is a key point here. To prevent doing some hacks in code (e.q. using service from one package in another one) we have to start by using the package-private scope for classes.

Let’s see how the structure of the project differs after improvement. In the classic approach, we have all the classes public.

Spring Boot project structure

Having a little bit of another structure we can keep only Facade and DTO’s public. This approach needs to be supported by a DDD approach that helps determine bounded contexts in the application. This means that each module realizes one part of the business.

Spring Boot project structure

Rest endpoints improvement

In the classic approach, to create a REST endpoint, we just create a new class and annotate it with @RestController annotation. Spring Boot reads this annotation and loads a new class into the application context. Sound like something that takes some time on startup. Let’s improve it and make out rest endpoints functional. To do this we have to add webflux project as a dependency.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Next thing to do it converting classic controllers like below…

@RestController
@RequestMapping("/boards/{boardId}")
public class BoardController {

    private final BoardService boardService;

    public BoardController(BoardService boardService) {
        this.boardService = boardService;
    }


    @GetMapping
    public DetailedBoardDto getBoard(@PathVariable("boardId") Long boardId){
        return boardService.getBoard(boardId);
    }

    @PostMapping("/aggregates")
    public AggregateDto createAggregate(@PathVariable("boardId") Long boardId,
                                        @RequestBody AggregateDto aggregate){
        return boardService.createAggregate(boardId, aggregate);
    }

    @PostMapping("/sticky-notes")
    public StickyNoteDto createStickyNote(@PathVariable("boardId") Long boardId,
                                        @RequestBody StickyNoteDto stickyNote){
        return boardService.createStickyNote(boardId, stickyNote);
    }

}

..into a functional server which contains all URIs:

@Configuration
class Server {

    private final BoardFacade boardFacade;
    private final TeamFacade teamFacade;

    Server(BoardFacade boardFacade, TeamFacade teamFacade) {
        this.boardFacade = boardFacade;
        this.teamFacade = teamFacade;
    }

    @Bean
    RouterFunction<ServerResponse> teamRouting() {
        return route(GET("/teams"),
                req -> ok().body(teamFacade.getTeams(), TeamDto.class))
                .andRoute(POST("/teams"),
                        req -> req.body(toMono(TeamDto.class))
                                .doOnNext(teamFacade::createTeam)
                                .then(ok().build()))
                .andRoute(POST("/teams/{teamId}/boards"),
                        req -> req.body(toMono(BoardDto.class))
                                .doOnNext(x -> teamFacade.createBoard(
                                        parseLong(req.pathVariable("teamId")), x)
                                ).then(ok().build()))
                .andRoute(GET("/teams/{teamId}/boards"),
                        req -> ok().body(teamFacade.getBoards(
                                parseLong(req.pathVariable("teamId"))), BoardDto.class))
                .andRoute(GET("/boards/{boardId}"),
                        req -> ok().body(
                                boardFacade.getBoard(
                                        parseLong(req.pathVariable("boardId"))
                                ), DetailedBoardDto.class))
                .andRoute(POST("/boards/{boardId}/aggregates"),
                        req -> req.body(toMono(AggregateDto.class))
                                .doOnNext(body -> boardFacade.createAggregate(
                                        parseLong(req.pathVariable("boardId")), body
                                ))
                                .then(ok().build()))
                .andRoute(POST("/boards/{boardId}/sticky-notes"),
                        req -> req.body(toMono(StickyNoteDto.class))
                                .doOnNext(body -> boardFacade.createStickyNote(
                                        parseLong(req.pathVariable("boardId")), body
                                ))
                                .then(ok().build()));
    }
}

In this way we have done two things:

– we are loading only one bean into context

– we started using the non-blocking IO with Netty server instead of servlets.

 

Introducing netty allows us further improvements in form of asynchronous programming but we can leave it for now.

Startup performance

Having application prepared for domain module testing we have part of improvement job done. Our application on startup loads only key objects to context. Fewer classes to scan equal less time to need starting context.

It found out, that such improvements caused that Spring Boot application is not only more maintainable but also starts much faster so restarting after changes are not so painful.

Below you can see screenshots from the startup of our simple application. In the case of bigger applications, we could expect a bigger difference between classic and improved applications.

Classic approach:

Classic Spring Booot startup

Improved one:

Improved Spring Booot startup

The reason why we see the difference here is that Spring Boot does a lot of magic with reflection which is slow. Spring does it of course on startup so it doesn’t decrease the speed of application but in case we need fast integration tests, we have to take care also about a fast startup.

Service improvements

Business code – part of the application that makes money for us – should be located in services. As long as we want to improve business we have to look into this part of the software fist.

We decided to improve our application by using another tool that allows manipulation of data in the database. We replaced JPA with JOOQ.

Why JOOQ?

It’s my first usage of JOOQ but I really liked it because it’s easy to configure. It is also really easy to write and read cause it looks like classic SQL syntax.

After some simple performance tests, I realized that this is much faster than JPA/Hibernate.
Using Postman I’ve set a test to execute 1000 POST calls and I’ve checked 3 cases: JPA with SQL logging, JPA and JOOQ.

Results look as follows:

– JPA (show-sql=true) : 8922ms
– JPA (show-sql=false): 7949ms
– JOOQ: 5703ms

Very interesting was also the first call for each case:

– JPA (show-sql=true) : 264ms
– JPA (show-sql=false): 286ms

– JOOQ: 41ms

 

The second was much faster:

– JPA (show-sql=true) : 11ms
– JPA (show-sql=false): 11ms

– JOOQ: 5ms

My conclusion after checking these numbers is that both tools have some built-in caching but still, JOOQ does it better than JPA.

The below chart shows the entire results of my test.

Chart performance

Summary

Sometimes to be able to improve software it’s enough to understand how the current one is working under the hood. This means that the key to increasing the performance of the application is a thorough understanding of basics and looking for alternatives.

About author

Hi,
my name is Michał. I’m software engineer. I like sharing my knowledge and ideas to help other people who struggle with technologies and design of todays IT systems or just want to learn something new.
If you want to be up to date leave your email below or leave invitation on LinkedIn, XING or Twitter.

Add your email to be up to date with content that I publish


Leave a Reply

Your email address will not be published. Required fields are marked *