diff --git a/README.md b/README.md
index 5ef72c62..9ac34405 100644
--- a/README.md
+++ b/README.md
@@ -61,6 +61,7 @@ Here are the different challenges :
- [Day 13: Find a way to eliminate the irrelevant, and amplify the essentials of those tests.](exercise/day13/docs/challenge.md)
- [Day 14: Do not use exceptions anymore.](exercise/day14/docs/challenge.md)
- [Day 15: Put a code under tests.](exercise/day15/docs/challenge.md)
+- [Day 16: Make this code immutable.](exercise/day16/docs/challenge.md)
### Solutions
A solution proposal will be published here every day during the `Advent Of Craft` containing `the code` and a `step by step` guide.
diff --git a/exercise/day16/docs/challenge.md b/exercise/day16/docs/challenge.md
new file mode 100644
index 00000000..118ce79f
--- /dev/null
+++ b/exercise/day16/docs/challenge.md
@@ -0,0 +1,25 @@
+## Day 16: Withstanding the change of time.
+
+Today, you are passing through a agitated sea.
+A tornado is raging not far from your area, and you have to change course.
+
+Today's exercise is about sustaining code.
+A code that is robust and that will weather changes.
+
+There are different avenues for code improvement:
+
+- We looked at breaking conditions, better naming, avoiding complexity
+ and even dependency check.
+
+But we are not talking about these refactoring steps.
+No, today we focus our effort into a simple question:
+
+_Is my code better mutable or immutable?_
+
+> **Day 16: Make this code immutable.**
+
+May you be protected on your crafting journey!
+
+- 💡HINT: Code from usage for this one.
+
+![snippet of the day](snippet.png)
\ No newline at end of file
diff --git a/exercise/day16/docs/snippet.png b/exercise/day16/docs/snippet.png
new file mode 100644
index 00000000..f49679ba
Binary files /dev/null and b/exercise/day16/docs/snippet.png differ
diff --git a/exercise/day16/pom.xml b/exercise/day16/pom.xml
new file mode 100644
index 00000000..7ab0f9cb
--- /dev/null
+++ b/exercise/day16/pom.xml
@@ -0,0 +1,28 @@
+
+
+ 4.0.0
+
+
+ com.advent-of-craft
+ advent-of-craft2023
+ 1.0-SNAPSHOT
+
+
+ blog-part3
+ 1.0-SNAPSHOT
+
+ 3.0.0
+
+
+
+
+ org.instancio
+ instancio-junit
+ ${instancio-junit.version}
+ test
+
+
+
+
\ No newline at end of file
diff --git a/exercise/day16/src/main/java/blog/Article.java b/exercise/day16/src/main/java/blog/Article.java
new file mode 100644
index 00000000..c96b8772
--- /dev/null
+++ b/exercise/day16/src/main/java/blog/Article.java
@@ -0,0 +1,37 @@
+package blog;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Article {
+ private final String name;
+ private final String content;
+ private final ArrayList comments;
+
+ public Article(String name, String content) {
+ this.name = name;
+ this.content = content;
+ this.comments = new ArrayList<>();
+ }
+
+ private void addComment(
+ String text,
+ String author,
+ LocalDate creationDate) throws CommentAlreadyExistException {
+ var comment = new Comment(text, author, creationDate);
+
+ if (comments.contains(comment)) {
+ throw new CommentAlreadyExistException();
+ } else comments.add(comment);
+ }
+
+ public void addComment(String text, String author) throws CommentAlreadyExistException {
+ addComment(text, author, LocalDate.now());
+ }
+
+ public List getComments() {
+ return comments;
+ }
+}
+
diff --git a/exercise/day16/src/main/java/blog/Comment.java b/exercise/day16/src/main/java/blog/Comment.java
new file mode 100644
index 00000000..c6b25bfc
--- /dev/null
+++ b/exercise/day16/src/main/java/blog/Comment.java
@@ -0,0 +1,6 @@
+package blog;
+
+import java.time.LocalDate;
+
+public record Comment(String text, String author, LocalDate creationDate) {
+}
diff --git a/exercise/day16/src/main/java/blog/CommentAlreadyExistException.java b/exercise/day16/src/main/java/blog/CommentAlreadyExistException.java
new file mode 100644
index 00000000..1eaca1d1
--- /dev/null
+++ b/exercise/day16/src/main/java/blog/CommentAlreadyExistException.java
@@ -0,0 +1,4 @@
+package blog;
+
+public class CommentAlreadyExistException extends Exception {
+}
\ No newline at end of file
diff --git a/exercise/day16/src/test/java/blog/ArticleBuilder.java b/exercise/day16/src/test/java/blog/ArticleBuilder.java
new file mode 100644
index 00000000..f1d49c83
--- /dev/null
+++ b/exercise/day16/src/test/java/blog/ArticleBuilder.java
@@ -0,0 +1,34 @@
+package blog;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ArticleBuilder {
+ public static final String AUTHOR = "Pablo Escobar";
+ public static final String COMMENT_TEXT = "Amazing article !!!";
+ private final HashMap comments;
+
+ public ArticleBuilder() {
+ comments = new HashMap<>();
+ }
+
+ public static ArticleBuilder anArticle() {
+ return new ArticleBuilder();
+ }
+
+ public ArticleBuilder commented() {
+ this.comments.put(COMMENT_TEXT, AUTHOR);
+ return this;
+ }
+
+ public Article build() throws CommentAlreadyExistException {
+ var article = new Article(
+ "Lorem Ipsum",
+ "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore"
+ );
+ for (Map.Entry comment : comments.entrySet()) {
+ article.addComment(comment.getKey(), comment.getValue());
+ }
+ return article;
+ }
+}
diff --git a/exercise/day16/src/test/java/blog/ArticleTests.java b/exercise/day16/src/test/java/blog/ArticleTests.java
new file mode 100644
index 00000000..68f05fdf
--- /dev/null
+++ b/exercise/day16/src/test/java/blog/ArticleTests.java
@@ -0,0 +1,73 @@
+package blog;
+
+import org.assertj.core.api.ThrowingConsumer;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import java.util.function.Function;
+
+import static blog.ArticleBuilder.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.instancio.Instancio.create;
+
+class ArticleTests {
+ private Article article;
+
+ @Test
+ void should_add_comment_in_an_article() throws CommentAlreadyExistException {
+ when(article -> article.addComment(COMMENT_TEXT, AUTHOR));
+ then(article -> {
+ assertThat(article.getComments()).hasSize(1);
+ assertComment(article.getComments().get(0), COMMENT_TEXT, AUTHOR);
+ });
+ }
+
+ @Test
+ void should_add_comment_in_an_article_containing_already_a_comment() throws Throwable {
+ final var newComment = create(String.class);
+ final var newAuthor = create(String.class);
+
+ when(ArticleBuilder::commented, article -> article.addComment(newComment, newAuthor));
+ then(article -> {
+ assertThat(article.getComments()).hasSize(2);
+ assertComment(article.getComments().getLast(), newComment, newAuthor);
+ });
+ }
+
+ @Nested
+ class Fail {
+ @Test
+ void when__adding_an_existing_comment() throws CommentAlreadyExistException {
+ var article = anArticle()
+ .commented()
+ .build();
+
+ assertThatThrownBy(() -> {
+ article.addComment(article.getComments().get(0).text(), article.getComments().get(0).author());
+ }).isInstanceOf(CommentAlreadyExistException.class);
+ }
+ }
+
+ private static void assertComment(Comment comment, String commentText, String author) {
+ assertThat(comment.text()).isEqualTo(commentText);
+ assertThat(comment.author()).isEqualTo(author);
+ }
+
+ private void when(ArticleBuilder articleBuilder, ThrowingConsumer act) throws CommentAlreadyExistException {
+ article = articleBuilder.build();
+ act.accept(article);
+ }
+
+ private void when(ThrowingConsumer act) throws CommentAlreadyExistException {
+ when(anArticle(), act);
+ }
+
+ private void when(Function options, ThrowingConsumer act) throws Throwable {
+ when(options.apply(anArticle()), act);
+ }
+
+ private void then(ThrowingConsumer act) {
+ act.accept(article);
+ }
+}
\ No newline at end of file
diff --git a/exercise/pom.xml b/exercise/pom.xml
index e99d88a5..617e134c 100644
--- a/exercise/pom.xml
+++ b/exercise/pom.xml
@@ -24,6 +24,7 @@
day13
day14
day15
+ day16