Immutable Spring Service With Lombok

Immutable Spring Service With Lombok

Project Lombok is an amazing Java library which makes it possible to write very concise code. It’s an incredibly powerful library which uses an annotation processor to generate code behind the scene.

Why use lombok

Let me show you a very simple example:

@Value
public class Person {
 String firstname;
 String lastname;
}

Is equivalent to the vanilla Java example:

@Value
public final class Person {
 private final String firstname;
 private final String lastname;

 public Person(final String firstname, final String lastname) {
   this.firstname = firstname;
   this.lastname = lastname;
 }

 public String getFirstname() {
   return firstname;
 }

 public String getLastname() {
   return lastname;
 }

 public boolean equals(Object other) {
   ...
 }

 public int hashCode() {
   ...
 }
}

And I skipped the verbose equals() and hashCode() methods content. We are going to see that Lombok not only allows you to write more concise code, but also immutable objects without the pain of code verbosity.

Legacy Spring service

Spring is a collection of very powerful libraries. We are going to be interested in the most famous one: the dependency injection engine. How many times have you seen this:

@Service
public class PersonServiceImpl implements PersonService {
 
 @Autowired
 private Repository<Person> repository;

 ...
}

I guess, you see this very often. Most developers inject dependencies inside @Service annotated classes using field injection. It’s purely because the constructor is verbose to write, even if it can be automatically generated by your IDE.

There are numerous issues with this service.

Wrong naming convention

The service is named PersonServiceImpl which is a wrong class naming convention. You all know the List interface from Java collections. Have you seen a ListImpl anywhere? No, you have LinkedList, ArrayList along with many others implementations.

Shy code

The class should be package protected: implementations of service interfaces should never be visible publicly. This is to discourage injecting the implementation instead of the service interface in other services. Shy codes tries to expose as little as possible knowledge to the other developers. It relies on the law of Demeter.

Mutability

The service is not immutable although it should be. Any method of the service can overwrite the injected repository. Also, a method could be called on the service before the repository would be injected, which can lead to a weird NullPointer.

Immutable classes also have a special meaning for the JVM which is beyond the scope of this article.

Lombokified Spring service

Thanks to Lombok, there is no reason to write field injected services anymore. Here is the same service with constructor injection and immutability:

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.experimental.FieldDefaults;

@Service
@AllArgsConstructor(access = PACKAGE)
@FieldDefaults(level = PRIVATE, makeFinal = true)
final class DatabasePersonService implements PersonService {
 @NonNull
 Repository<Person> repository;

 ...
}

You have now a fully immutable constructor injected service without the pain of writing boiler-plate code yourself.

Service

@Service

This is the Spring service annotation. It instructs Spring that this class is a Service which needs to be instantiated.

FieldDefault

@FieldDefaults(level = PRIVATE, makeFinal = true)

This annotation indicates that the class field are all private final. This makes the service immutable. Now that all fields must be initialized at construction time, we need to add an Autowired constructor with all the fields.

AllArgsConstructor

@AllArgsConstructor(access = PACKAGE)

This annotation generates a package-protected constructor.

Code generation

Lombok generates code in the annotation processing phase during compilation which means there is no runtime overhead (like with Reflection).

Conclusion

Some developers are reluctant to massively use annotations due to the magic done behind the scene. It’s necessary to understand the code lombok generates, but there is nothing difficult there. Once you have tried Lombok, you can’t go back. It removes so much boiler plate code it’s well worth learning to use it.

Our entire backend code is written with Lombok from the beginning. It saved us from writing thousands of lines of code that can be safely generated. This is part of our Automate everything you can rule.

By - CTO.
Tags: Java Lombok Spring Import

Comments

Oleg  

Hi Jérôme! How do create test for this service with constructor injection and immutability? Could you add sample with Mock or JUnit for example?
Reply

Jerome
In reply to Oleg
 

Hi Oleg,

You simply create an instance of the service by using new when performing regular junits (with mocks). I’m not sure to understand what you find difficult here. Then, you mock all the dependencies.

Otherwise, for tests running in a Spring container, you just have to create a mock Spring @Config like this in your test sources:

@Config
class MockConfig {

  MyService myService() {
    return Mockito.mock(MyService.class);
  }
}

Or you can even using Spring’s @MockBean right in your JUnit running with Spring. See Spring Testing for more information.

 

Thank you

Your comment has been submitted and will be published once it has been approved.

OK

OOPS!

Your post has failed. Please return to the page and try again. Thank You!

OK