• Stars
    star
    128
  • Rank 281,044 (Top 6 %)
  • Language
  • License
    MIT License
  • Created almost 6 years ago
  • Updated about 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An opinionated guide on developing web applications with Spring Boot.

Spring Boot Style Guide

An opinionated guide on developing web applications with Spring Boot. Inspired by Airbnb JavaScript Style Guide.

Join the chat at https://gitter.im/helpermethod/spring-boot-style-guide License

Table of Contents

Dependency Injection

  • Use constructor injection. Avoid field injection.

Why? Constructor injection makes dependencies explicit and forces you to provide all mandatory dependencies when creating instances of your component.

// bad
public class PersonService {
    @AutoWired
    private PersonRepository personRepositoy;
}

// good
public class PersonService {
    private final PersonRepository personRepository;

    // if the class has only one constructor, @Autowired can be omitted
    public PersonService(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }
}    
  • Avoid single implementation interfaces.

Why? A class already exposes an interface: its public members. Adding an identical interface definition makes the code harder to navigate and violates YAGNI.

What about testing? Earlier mocking frameworks were only capable of mocking interfaces. Recent frameworks like Mockito can also mock classes.

// bad
public interface PersonService {
    List<Person> getPersons();
}

public class PersonServiceImpl implements PersonService {
    public List<Person> getPersons() {
        // more code
    }
}

// good
public class PersonService {
    public List<Person> getPersons() {
        // more code
    }
}

⬆ back to top

Controllers

  • Use @RestController when providing a RESTful API.
// bad
@Controller
public class PersonController {
    @ResponseBody
    @GetMapping("/persons/{id}")
    public Person show(@PathVariable long id) {
        // more code
    }
}

// good
@RestController
public class PersonController {
    @GetMapping("/persons/{id}")
    public Person show(@PathVariable long id) {
        // more code
    }
}
  • Use @GetMapping, @PostMapping etc. instead of @RequestMapping.
// bad
@RestController
public class PersonController {
    @RequestMapping(method = RequestMethod.GET, value = "/persons/{id}")
    public Person show(@PathVariable long id) {
        // more code
    }
}

// good
@RestController
public class PersonController {
    @GetMapping("/persons/{id}")
    public Person show(@PathVariable long id) {
        // more code
    }
}
  • Simplify your controller keeping it thin

Why? To avoid SRP violations;

Where should I put my business logic? Keep the bussines logic encapsulated into your services or specialized classes;

// bad
@PostMapping("/users")
public ResponseEntity<Void> postNewUser(@RequestBody UserRequest userRequest) {
    if (userRequest.isLessThanEighteenYearsOld()) {
        throw new IllegalArgumentException("Sorry, only users greater or equal than 18 years old.");
    }

    if (!userRequest.hasJob()) {
        throw new IllegalArgumentException("Sorry, only users working.");
    }

    if (!this.userService.hasUsernameAvailable(userRequest.getUsername())) {
        throw new IllegalArgumentException(String.format("Sorry, [%s] is not an available username.", userRequest.getUsername()));
    }

    this.userService.createNewUser(userRequest);

    return ResponseEntity.status(HttpStatus.CREATED).build();
}

// good
@PostMapping("/users")
public ResponseEntity<Void> postNewUser(@RequestBody UserRequest userRequest) {
    this.userService.createNewUser(userRequest);
    return ResponseEntity.status(HttpStatus.CREATED).build();
}

public class UserService {

    // variables declaration

    public void createNewUser(UserRequest userRequest) {
        this.validateNewUser(userRequest);
        UserEntity newUserEntity = this.userMapper.mapToEntity(userRequest);
        this.userRepository.save(newUserEntity);
    }

    private void validateNewUser(UserRequest userRequest) {
        // business validations
    }
}

⬆ back to top

Serialization

  • Do not map your JSON objects to JavaBeans.

Why? JavaBeans are mutable and split object construction across multiple calls.

// bad
public class Person {
    private String firstname;
    private String lastname;

    public void setFirstname() {
        this.firstname = firstname;
    }

    public String getFirstname() {
        return firstname;
    }

    public void setLastname() {
        this.lastname = lastname;
    }

    public String getLastname() {
        return lastname;
    }
}

// good
public class Person {
    private final String firstname;
    private final String lastname;

    // requires your code to be compiled with a Java 8 compliant compiler 
    // with the -parameter flag turned on
    // as of Spring Boot 2.0 or higher, this is the default
    @JsonCreator
    public Person(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }

    public String getFirstname() {
        return firstname;
    }

    public String getLastname() {
        return lastname;
    }
}

// best
public class Person {
    private final String firstname;
    private final String lastname;

    // if the class has a only one constructor, @JsonCreator can be omitted
    public Person(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }

    public String getFirstname() {
        return firstname;
    }

    public String getLastname() {
        return lastname;
    }
}

⬆ back to top

Testing

  • Keep Spring out of your unit tests.
class PersonServiceTests {
    @Test
    void testGetPersons() {
        // given
        PersonRepository personRepository = mock(PersonRepository.class);
        when(personRepository.findAll()).thenReturn(List.of(new Person("Oliver", "Weiler")));

        PersonService personService = new PersonService(personRepository);

        // when
        List<Person> persons = personService.getPersons();

        // then
        assertThat(persons).extracting(Person::getFirstname, Person::getLastname).containsExactly("Oliver", "Weiler");
    }
}

Why? AssertJ is more actively developed, requires only one static import, and allows you to discover assertions through autocompletion.

// bad
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.empty;

assertThat(persons), is(not(empty())));

// good
import static org.assertj.core.api.Assertions.assertThat;

assertThat(persons).isNotEmpty();

⬆ back to top

More Repositories

1

zip-forge

A tiny, formatter-friendly Java DSL for creating ZIP files.
Java
36
star
2

connor

A commandline tool for resetting Kafka Connect source connector offsets.
Java
27
star
3

molten-json

A fluent Java 8 DSL for building JSON documents.
Java
19
star
4

bash-specs

A BDD testing framework for Bash inspired by Jasmine and roundup.
Shell
12
star
5

p2e

Convert Spring configuration property names to environment variable names.
PowerShell
9
star
6

graalvm-native-image-toolchain

8
star
7

lamda

A functional programming library for Lua, inspired by Ramda.
Lua
7
star
8

pk

Killing processes by port number, with style.
Shell
5
star
9

gscope

Kotlin's scoping functions for Groovy.
Groovy
4
star
10

up

Climbing the directory tree at the speed of light.
Shell
4
star
11

awesome-git-hooks

A collection of awesome git hooks.
Shell
4
star
12

termplates

Mustache Templates for the Commandline
Java
3
star
13

mr

Open GitLab Merge Requests from the commandline.
Shell
3
star
14

aoc-2022

Kotlin
2
star
15

node-super-dev

A Vagrantfile for setting up Node development environments.
Vim Script
2
star
16

speed-tracer

xtrace on steroids
Shell
1
star
17

vendor-announce

Kotlin
1
star
18

homebrew-tap

Ruby
1
star
19

cloud-config

1
star
20

distributed-geo-service

Shell
1
star
21

helpermethod

1
star
22

aws-azure-login-bash-completion

Bash completion for aws-azure-login
Shell
1
star
23

spring-cloud-config-git

1
star
24

spring-boot-actuator-workshop

Shell
1
star
25

spring-cloud-workshop

Shell
1
star
26

docker-luarocks

A Docker image for building Lua applications.
Dockerfile
1
star
27

i-can-haz-sdk

Java
1
star
28

lipstick

1
star
29

filenamify

Lua
1
star
30

macports

Tcl
1
star
31

docker-lua

A Docker image for running Lua applications.
Dockerfile
1
star
32

scoop-helpermethod

1
star
33

jbang-catalog

1
star
34

mst-cli

Java
1
star
35

bash-completion-extension

A JUnit 5 extension for testing Bash completions
Kotlin
1
star