§Immutable Objects
An immutable object is an object that cannot be modified after it was created.
Immutable objects have two great advantages:
- Code based on immutable objects is clearer and likelier to be correct. Bugs involving unexpected changes simply can’t occur.
- Multiple threads can safely access immutable objects concurrently.
In Lagom, immutable objects are required in several places, such as:
- Service request and response types
- Persistent entity commands, events, and states
- Publish and subscribe messages
§Mutable vs. immutable
Here is an example of a mutable object:
public class MutableUser {
private String name;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
The setter methods can be used to modify the object after construction.
Here, by contrast, is an immutable object:
public class ImmutableUser {
private final String name;
private final String email;
public ImmutableUser(String name, String email) {
this.name = name;
this.email = email;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
}
All fields are final and are assigned at construction time. There are no setter methods.
§Immutables
Lagom doesn’t care how you define your immutable objects. You can write out the constructor and getters by hand, as in the above sample. But we recommend using the Immutables tool to generate them instead. Using Immutables is easier and less error-prone than writing everything out by hand, and the resulting code is shorter and easier to read.
Here is the corresponding definition of a User
, like the above ImmutableUser
:
@Value.Immutable
@ImmutableStyle
public interface AbstractUser {
String getName();
String getEmail();
}
For free you get things like:
- builders (very convenient when your class has many fields)
- correct
equals
,hashCode
,toString
implementations - copy methods to make new instances based on old ones, e.g.
withEmail
Immutables supports different “styles” of object. Compared to the default style, @ImmutableStyle
follows a convention that the abstract class or interface name starts with Abstract
and that is trimmed from the generated class, e.g. AbstractUser
vs User
. To use the @ImmutableStyle
annotation you need to add lagomJavadslImmutables
to your project’s library dependencies. Here is an example:
lazy val usersApi = (project in file("usersApi"))
.settings(libraryDependencies += lagomJavadslImmutables)
Immutables integrates with popular IDEs. Follow the instructions for Eclipse or IntelliJ IDEA to add the Immutables annotation processor to your IDE.
§Collections
If an immutable object contains a collection, that collection must be immutable too.
Here is an example of an object with a mutable collection in it:
public class MutableUser2 {
private final String name;
private final List<String> phoneNumbers;
public MutableUser2(String name, List<String> phoneNumbers) {
this.name = name;
this.phoneNumbers = phoneNumbers;
}
public String getName() {
return name;
}
public List<String> getPhoneNumbers() {
return phoneNumbers;
}
}
The object might look like immutable since it only has final fields and no setters, but the List
of phoneNumbers
is mutable, because it can be changed by code like user.getPhoneNumbers().add("+468123456")
.
One possible fix would be to make a defensive copy of the List
in the constructor and use Collections.unmodifiableList
in the getter, like this:
public class ImmutableUser2 {
private final String name;
private final List<String> phoneNumbers;
public ImmutableUser2(String name, List<String> phoneNumbers) {
this.name = name;
this.phoneNumbers = new ArrayList<>(phoneNumbers);
}
public String getName() {
return name;
}
public List<String> getPhoneNumbers() {
return Collections.unmodifiableList(phoneNumbers);
}
}
But it is easy to make mistakes when coding this way, so we again recommend letting Immutable handle it for you, as follows.
Here is a new definition of a User2
, like the above ImmutableUser2
:
@Value.Immutable
@ImmutableStyle
public interface AbstractUser2 {
String getName();
List<String> getPhoneNumbers();
}
The getPhoneNumbers
method will return an instance of com.google.common.collect.ImmutableList
.
§Guava and PCollections
As seen above, Immutables uses Guava immutable collections by default.
The Guava collections are certainly better for this purpose than plain java.util
collections. However, the Guava collections are cumbersome and inefficient for some common operations (for example, making a slightly modified copy of an existing collection).
Therefore, we recommend PCollections, which is a collection library that is designed from the ground up for immutability and efficiency.
This is how the above example looks like using a PCollection:
import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
import org.immutables.value.Value;
import org.pcollections.PVector;
@Value.Immutable
@ImmutableStyle
public interface AbstractUser3 {
String getName();
PVector<String> getPhoneNumbers();
}
This is how to define an optional collection initialized with the empty collection:
import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
import org.immutables.value.Value;
import org.pcollections.PVector;
import org.pcollections.TreePVector;
@Value.Immutable
@ImmutableStyle
public interface AbstractUser4 {
String getName();
@Value.Default
default PVector<String> getPhoneNumbers() {
return TreePVector.empty();
}
}
§“Persistent” collections
There are two different and potentially confusing usages of the word “persistent” with respect to data.
You will see references, in the PCollections documentation and elsewhere, to “persistent” data structures. There, the word “persistent” means that even when you construct a modified copy of a collection, the original “persists”.
In the rest of this documentation, “persistent” refers instead to persistent storage, as in Persistent Entities and the examples below.
§Further reading
The Immutables documentation has more details on immutable collections here.
§Example of PersistentEntity Commands
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.lightbend.lagom.javadsl.immutable.ImmutableStyle;
import com.lightbend.lagom.javadsl.persistence.PersistentEntity;
import com.lightbend.lagom.serialization.Jsonable;
import org.immutables.value.Value;
import akka.Done;
public interface BlogCommand extends Jsonable {
//#AddPost
@Value.Immutable
@ImmutableStyle
@JsonDeserialize(as = AddPost.class)
interface AbstractAddPost
extends BlogCommand, PersistentEntity.ReplyType<AddPostDone> {
@Value.Parameter
PostContent getContent();
}
//#AddPost
@Value.Immutable
@ImmutableStyle
@JsonDeserialize(as = AddPostDone.class)
interface AbstractAddPostDone extends Jsonable {
@Value.Parameter
String getPostId();
}
@Value.Immutable(singleton = true, builder = false)
@ImmutableStyle
@JsonDeserialize(as = GetPost.class)
public abstract class AbstractGetPost implements BlogCommand, PersistentEntity.ReplyType<PostContent> {
protected AbstractGetPost() {
}
}
@Value.Immutable
@ImmutableStyle
@JsonDeserialize(as = ChangeBody.class)
interface AbstractChangeBody extends BlogCommand, PersistentEntity.ReplyType<Done> {
@Value.Parameter
String getBody();
}
@Value.Immutable(singleton = true, builder = false)
@ImmutableStyle
@JsonDeserialize(as = Publish.class)
public abstract class AbstractPublish implements BlogCommand, PersistentEntity.ReplyType<Done> {
protected AbstractPublish() {
}
}
}
A few things worth noting here:
@Value.Parameter
can be added to one or more properties to generate constructor method, which is more convenient than the full builder for classes with only a few propertiessingleton = true
can be used for objects that don’t have any properties, i.e. singleton instances, also note that the visibility of the constructor should be reduced for such classes
§Example of PersistentEntity Events
interface BlogEvent extends Jsonable, AggregateEvent<BlogEvent> {
@Override
default public AggregateEventTag<BlogEvent> aggregateTag() {
return BlogEventTag.INSTANCE;
}
@Value.Immutable
@ImmutableStyle
@JsonDeserialize(as = PostAdded.class)
interface AbstractPostAdded extends BlogEvent {
String getPostId();
PostContent getContent();
}
@Value.Immutable
@ImmutableStyle
@JsonDeserialize(as = BodyChanged.class)
interface AbstractBodyChanged extends BlogEvent {
@Value.Parameter
String getBody();
}
@Value.Immutable
@ImmutableStyle
@JsonDeserialize(as = PostPublished.class)
interface AbstractPostPublished extends BlogEvent {
@Value.Parameter
String getPostId();
}
}
§Example of PersistentEntity State
@Value.Immutable
@ImmutableStyle
@JsonDeserialize(as = BlogState.class)
public abstract class AbstractBlogState implements Jsonable {
public static final BlogState EMPTY = BlogState.of(Optional.empty());
@Value.Parameter
public abstract Optional<PostContent> getContent();
@Value.Default
@JsonProperty("isPublished")
public boolean isPublished() {
return false;
}
public BlogState withBody(String body) {
if (isEmpty())
throw new IllegalStateException("Can't set body without content");
return BlogState.builder().from(this).content(
Optional.of(getContent().get().withBody(body))).build();
}
@JsonIgnore
public boolean isEmpty() {
return getContent() == null;
}
}
@Value.Immutable
@ImmutableStyle
@JsonDeserialize(as = PostContent.class)
public interface AbstractPostContent extends Jsonable {
@Value.Parameter
public String getTitle();
@Value.Parameter
public String getBody();
}
A few things worth noting here:
- Java 8’s
Optional
type can be used for optional properties - You can add more methods to the classes, such as
withBody
andisEmpty
above - You can define default values for optional properties, e.g.
isPublished
above