From f5795a1a4193f3b13ed0ba4917888bd1af0a2915 Mon Sep 17 00:00:00 2001 From: mperor <mpietryga93@gmail.com> Date: Thu, 27 Feb 2025 20:01:28 +0100 Subject: [PATCH] Add value object examples --- .../code/ddd/value/object/DateTimeRange.java | 19 ++++++++ .../code/ddd/value/object/EmailAddress.java | 14 ++++++ .../clean/code/ddd/value/object/Money.java | 33 ++++++++++++++ .../ddd/value/object/ValueObjectTest.java | 43 +++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/DateTimeRange.java create mode 100644 CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/EmailAddress.java create mode 100644 CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/Money.java create mode 100644 CleanCode/src/test/java/pl/mperor/lab/java/clean/code/ddd/value/object/ValueObjectTest.java diff --git a/CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/DateTimeRange.java b/CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/DateTimeRange.java new file mode 100644 index 0000000..cbc60d0 --- /dev/null +++ b/CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/DateTimeRange.java @@ -0,0 +1,19 @@ +package pl.mperor.lab.java.clean.code.ddd.value.object; + +import java.time.LocalDateTime; + +record DateTimeRange(LocalDateTime start, LocalDateTime end) { + + DateTimeRange { + if (start == null || end == null) { + throw new IllegalArgumentException("Date range start & end cannot be null!"); + } + if (start.isAfter(end)) { + throw new IllegalArgumentException("Date range start cannot be after end!"); + } + } + + boolean isWithinRange(LocalDateTime dateTime) { + return !dateTime.isBefore(start) && !dateTime.isAfter(end); + } +} diff --git a/CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/EmailAddress.java b/CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/EmailAddress.java new file mode 100644 index 0000000..48ab527 --- /dev/null +++ b/CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/EmailAddress.java @@ -0,0 +1,14 @@ +package pl.mperor.lab.java.clean.code.ddd.value.object; + +import java.util.regex.Pattern; + +record EmailAddress(String email) { + + private static final Pattern emailPattern = Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$"); + + EmailAddress { + if (email == null || emailPattern.asMatchPredicate().negate().test(email)) { + throw new IllegalArgumentException("Invalid email address!"); + } + } +} diff --git a/CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/Money.java b/CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/Money.java new file mode 100644 index 0000000..a6ff19f --- /dev/null +++ b/CleanCode/src/main/java/pl/mperor/lab/java/clean/code/ddd/value/object/Money.java @@ -0,0 +1,33 @@ +package pl.mperor.lab.java.clean.code.ddd.value.object; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +record Money(BigDecimal amount) { + + Money(BigDecimal amount) { + if (amount == null) { + throw new IllegalArgumentException("Amount must not be null"); + } + if (amount.compareTo(BigDecimal.ZERO) < 0) { + throw new IllegalArgumentException("Amount cannot be negative!"); + } + this.amount = setScale(amount); + } + + private BigDecimal setScale(BigDecimal input) { + return input.setScale(2, RoundingMode.HALF_EVEN); + } + + public Money add(Money other) { + return new Money(this.amount.add(other.amount)); + } + + public Money subtract(Money other) { + return new Money(this.amount.subtract(other.amount)); + } + + public static Money of(BigDecimal amount) { + return new Money(amount); + } +} diff --git a/CleanCode/src/test/java/pl/mperor/lab/java/clean/code/ddd/value/object/ValueObjectTest.java b/CleanCode/src/test/java/pl/mperor/lab/java/clean/code/ddd/value/object/ValueObjectTest.java new file mode 100644 index 0000000..0548ff1 --- /dev/null +++ b/CleanCode/src/test/java/pl/mperor/lab/java/clean/code/ddd/value/object/ValueObjectTest.java @@ -0,0 +1,43 @@ +package pl.mperor.lab.java.clean.code.ddd.value.object; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.LocalDateTime; + + +class ValueObjectTest { + + @Test + void shouldAllowToUseMoneyAsValueObject() { + var money = Money.of(BigDecimal.ZERO); + Assertions.assertEquals(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_EVEN), money.amount()); + + Assertions.assertThrows(IllegalArgumentException.class, () -> new Money(null)); + Assertions.assertThrows(IllegalArgumentException.class, () -> new Money(new BigDecimal("-1"))); + + var three = Money.of(BigDecimal.ONE).add(Money.of(BigDecimal.TWO)); + Assertions.assertEquals(new BigDecimal("3.00"), three.amount()); + Assertions.assertEquals(new BigDecimal("2.00"), three.subtract(Money.of(BigDecimal.ONE)).amount()); + } + + @Test + void shouldAllowToUseEmailAddressAsValueObject() { + var email = new EmailAddress("john.doe@example.com"); + Assertions.assertEquals("john.doe@example.com", email.email()); + Assertions.assertThrows(IllegalArgumentException.class, () -> new EmailAddress(null)); + Assertions.assertThrows(IllegalArgumentException.class, () -> new EmailAddress("not an email @.wtf")); + } + + @Test + void shouldAllowToUseDateTimeRangeAsValueObject() { + Assertions.assertTrue(new DateTimeRange(LocalDateTime.MIN, LocalDateTime.MAX) + .isWithinRange(LocalDateTime.now())); + Assertions.assertTrue(new DateTimeRange(LocalDateTime.MIN, LocalDateTime.MAX) + .isWithinRange(LocalDateTime.MIN)); + Assertions.assertTrue(new DateTimeRange(LocalDateTime.MIN, LocalDateTime.MAX) + .isWithinRange(LocalDateTime.MAX)); + } +} \ No newline at end of file