findAll();
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/bike/NumberPlate.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.bike;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | @ValueObject
6 | public class NumberPlate {
7 |
8 | private String value;
9 |
10 | private NumberPlate(String value) {
11 | this.value = value;
12 | }
13 |
14 | public String value() {
15 | return this.value;
16 | }
17 |
18 | public static NumberPlate numberPlate(String value) {
19 | return new NumberPlate(value);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/WalletOwner.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | @ValueObject
6 | public class WalletOwner {
7 |
8 | private final String value;
9 |
10 | private WalletOwner(String value) {
11 | this.value = value;
12 | }
13 |
14 | public String value() {
15 | return this.value;
16 | }
17 |
18 | public static WalletOwner walletOwner(String value) {
19 | return new WalletOwner(value);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/UserHandle.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | @ValueObject
6 | public class UserHandle {
7 |
8 | private final String value;
9 |
10 | private UserHandle(String value) {
11 | this.value = value;
12 | }
13 |
14 | public String value() {
15 | return this.value;
16 | }
17 |
18 | public static UserHandle userHandle(String value) {
19 | return new UserHandle(value);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/support/Validations.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain.support;
2 |
3 | import static com.github.cstettler.dddttc.registration.domain.support.MandatoryParameterMissingException.mandatoryPropertyMissing;
4 |
5 | public class Validations {
6 |
7 | private Validations() {
8 | }
9 |
10 | public static void validateNotNull(String parameterName, Object parameterValue) {
11 | if (parameterValue == null) {
12 | throw mandatoryPropertyMissing(parameterName);
13 | }
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/user/UserNotExistingException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.user;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class UserNotExistingException extends RuntimeException {
7 |
8 | private UserNotExistingException(UserId userid) {
9 | super("user '" + userid.value() + "' does not exist");
10 | }
11 |
12 | public static UserNotExistingException userNotExisting(UserId userid) {
13 | return new UserNotExistingException(userid);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-support/src/test/java/com/github/cstettler/dddttc/support/infrastructure/event/TestDomainEvent.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.support.infrastructure.event;
2 |
3 | import com.github.cstettler.dddttc.stereotype.DomainEvent;
4 |
5 | @DomainEvent
6 | class TestDomainEvent {
7 |
8 | private String value;
9 |
10 | private TestDomainEvent(String value) {
11 | this.value = value;
12 | }
13 |
14 | String value() {
15 | return this.value;
16 | }
17 |
18 | static TestDomainEvent testDomainEvent(String value) {
19 | return new TestDomainEvent(value);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/TransactionReference.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | @ValueObject
6 | class TransactionReference {
7 |
8 | private final String value;
9 |
10 | private TransactionReference(String value) {
11 | this.value = value;
12 | }
13 |
14 | String value() {
15 | return this.value;
16 | }
17 |
18 | static TransactionReference transactionReference(String value) {
19 | return new TransactionReference(value);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/bike/BikeNotExistingException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.bike;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class BikeNotExistingException extends RuntimeException {
7 |
8 | private BikeNotExistingException(NumberPlate numberPlate) {
9 | super("bike '" + numberPlate.value() + "' does not exist");
10 | }
11 |
12 | public static BikeNotExistingException bikeNotExisting(NumberPlate numberPlate) {
13 | return new BikeNotExistingException(numberPlate);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-stereotype/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | com.github.cstettler.dddttc
8 | ddd-to-the-code-workshop
9 | 0.0.1-SNAPSHOT
10 |
11 |
12 | ddd-to-the-code-workshop-stereotype
13 | jar
14 |
15 |
16 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/WalletNotExistingException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class WalletNotExistingException extends RuntimeException {
7 |
8 | private WalletNotExistingException(WalletOwner walletOwner) {
9 | super("wallet '" + walletOwner.value() + "' does not exist");
10 | }
11 |
12 | public static WalletNotExistingException walletNotExisting(WalletOwner walletOwner) {
13 | return new WalletNotExistingException(walletOwner);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/booking/BookingNotExistingException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.booking;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class BookingNotExistingException extends RuntimeException {
7 |
8 | private BookingNotExistingException(BookingId bookingId) {
9 | super("booking '" + bookingId.value() + "' does not exist");
10 | }
11 |
12 | public static BookingNotExistingException bookingNotExisting(BookingId bookingId) {
13 | return new BookingNotExistingException(bookingId);
14 | }
15 |
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/etc/architecture-governance/reports.adoc:
--------------------------------------------------------------------------------
1 | == Reports
2 |
3 | [[dddttc:StereotypesUsageReport.graphml]]
4 | .Report showing all dependencies between stereotypes (GraphML)
5 | [source,cypher,role=concept,requiresConcepts="dddttc:StereotypeConcept"]
6 | ----
7 | MATCH (m:Stereotype)-[d:DEPENDS_ON]->(n:Stereotype)
8 | RETURN m, d, n
9 | ----
10 |
11 |
12 | [[dddttc:StereotypesUsageReport]]
13 | .Report showing all dependencies between stereotypes (PlantUML)
14 | [source,cypher,role=concept,requiresConcepts="dddttc:*Concept",reportType="plantuml-component-diagram"]
15 | ----
16 | MATCH (m:Stereotype)-[d:DEPENDS_ON]->(n:Stereotype)
17 | RETURN m, d, n
18 | ----
19 |
20 | (more reports to follow)
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/WalletAlreadyExistsException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class WalletAlreadyExistsException extends RuntimeException {
7 |
8 | private WalletAlreadyExistsException(WalletOwner walletOwner) {
9 | super("wallet '" + walletOwner.value() + "' already exists");
10 | }
11 |
12 | public static WalletAlreadyExistsException walletAlreadyExists(WalletOwner walletOwner) {
13 | return new WalletAlreadyExistsException(walletOwner);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/PhoneNumberNotSwissException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class PhoneNumberNotSwissException extends RuntimeException {
7 |
8 | private PhoneNumberNotSwissException(PhoneNumber phoneNumber) {
9 | super("phone number '" + phoneNumber.value() + "' is not swiss");
10 | }
11 |
12 | static PhoneNumberNotSwissException phoneNumberNotSwiss(PhoneNumber phoneNumber) {
13 | return new PhoneNumberNotSwissException(phoneNumber);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/UserRegistrationRepository.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.Repository;
4 |
5 | @Repository
6 | public interface UserRegistrationRepository {
7 |
8 | void add(UserRegistration userRegistration) throws UserHandleAlreadyInUseException;
9 |
10 | void update(UserRegistration userRegistration) throws UserRegistrationNotExistingException;
11 |
12 | UserRegistration get(UserRegistrationId userRegistrationId) throws UserRegistrationNotExistingException;
13 |
14 | UserRegistration find(UserHandle userHandle);
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-stereotype/src/main/java/com/github/cstettler/dddttc/stereotype/DomainService.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.stereotype;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.Target;
6 |
7 | import static java.lang.annotation.ElementType.TYPE;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | /**
11 | * Represents a domain service implementing business logic not directly assignable to a specific aggregate. Domain
12 | * services may also provide domain event handlers.
13 | */
14 | @Target(TYPE)
15 | @Retention(RUNTIME)
16 | @Documented
17 | public @interface DomainService {
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/UserHandleAlreadyInUseException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class UserHandleAlreadyInUseException extends RuntimeException {
7 |
8 | private UserHandleAlreadyInUseException(UserHandle userHandle) {
9 | super("user handle '" + userHandle.value() + "' is already in use");
10 | }
11 |
12 | public static UserHandleAlreadyInUseException userHandleAlreadyInUse(UserHandle userHandle) {
13 | return new UserHandleAlreadyInUseException(userHandle);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/booking/BookingAlreadyCompletedException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.booking;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class BookingAlreadyCompletedException extends RuntimeException {
7 |
8 | private BookingAlreadyCompletedException(BookingId bookingId) {
9 | super("booking '" + bookingId.value() + "' has already been completed");
10 | }
11 |
12 | public static BookingAlreadyCompletedException bookingAlreadyCompleted(BookingId bookingId) {
13 | return new BookingAlreadyCompletedException(bookingId);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-stereotype/src/main/java/com/github/cstettler/dddttc/stereotype/Aggregate.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.stereotype;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Target;
5 |
6 | import static java.lang.annotation.ElementType.TYPE;
7 |
8 | /**
9 | * Represents an aggregate with core domain logic and related state. An aggregate ensures its domain invariants and
10 | * represents the minimal scope of a business transaction. Aggregate instances are identified by their aggregate id.
11 | * Equality between two aggregates instances is defined by their type and their aggregate id.
12 | */
13 | @Target(TYPE)
14 | @Documented
15 | public @interface Aggregate {
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/WalletInitializedEvent.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.DomainEvent;
4 |
5 | @DomainEvent
6 | class WalletInitializedEvent {
7 |
8 | private final WalletOwner walletOwner;
9 |
10 | private WalletInitializedEvent(WalletOwner walletOwner) {
11 | this.walletOwner = walletOwner;
12 | }
13 |
14 | WalletOwner walletOwner() {
15 | return this.walletOwner;
16 | }
17 |
18 | static WalletInitializedEvent walletInitialized(WalletOwner walletOwner) {
19 | return new WalletInitializedEvent(walletOwner);
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-stereotype/src/main/java/com/github/cstettler/dddttc/stereotype/ApplicationService.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.stereotype;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.Target;
6 |
7 | import static java.lang.annotation.ElementType.TYPE;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | /**
11 | * Represents an application service responsible for providing access to the domain to external clients. An application
12 | * service orchestrates use cases, but does not contain business logic.
13 | */
14 | @Target(TYPE)
15 | @Retention(RUNTIME)
16 | @Documented
17 | public @interface ApplicationService {
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/PhoneNumberNotYetVerifiedException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class PhoneNumberNotYetVerifiedException extends RuntimeException {
7 |
8 | private PhoneNumberNotYetVerifiedException(PhoneNumber phoneNumber) {
9 | super("phone number '" + phoneNumber.value() + "' has not yet been verified");
10 | }
11 |
12 | static PhoneNumberNotYetVerifiedException phoneNumberNotYetVerified(PhoneNumber phoneNumber) {
13 | return new PhoneNumberNotYetVerifiedException(phoneNumber);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/support/MandatoryParameterMissingException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain.support;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class MandatoryParameterMissingException extends RuntimeException {
7 |
8 | private MandatoryParameterMissingException(String parameterName) {
9 | super("mandatory parameter '" + parameterName + "' is missing");
10 | }
11 |
12 | static MandatoryParameterMissingException mandatoryPropertyMissing(String parameterName) {
13 | return new MandatoryParameterMissingException(parameterName);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/PhoneNumberAlreadyVerifiedException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class PhoneNumberAlreadyVerifiedException extends RuntimeException {
7 |
8 | private PhoneNumberAlreadyVerifiedException(PhoneNumber phoneNumber) {
9 | super("phone number '" + phoneNumber.value() + "' has already been verified");
10 | }
11 |
12 | static PhoneNumberAlreadyVerifiedException phoneNumberAlreadyVerified(PhoneNumber phoneNumber) {
13 | return new PhoneNumberAlreadyVerifiedException(phoneNumber);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/BookingAlreadyBilledException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class BookingAlreadyBilledException extends RuntimeException {
7 |
8 | private BookingAlreadyBilledException(Wallet wallet, Booking booking) {
9 | super("booking '" + booking.id().value() + " has already been billed to wallet '" + wallet.walletOwner().value() + "'");
10 | }
11 |
12 | static BookingAlreadyBilledException bookingAlreadyBilled(Wallet wallet, Booking booking) {
13 | return new BookingAlreadyBilledException(wallet, booking);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/PhoneNumber.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | @ValueObject
6 | public class PhoneNumber {
7 |
8 | private final String value;
9 |
10 | private PhoneNumber(String value) {
11 | this.value = value;
12 | }
13 |
14 | public String value() {
15 | return this.value;
16 | }
17 |
18 | boolean isSwiss() {
19 | return this.value != null && (this.value.startsWith("+41") || this.value.startsWith("0041"));
20 | }
21 |
22 | public static PhoneNumber phoneNumber(String value) {
23 | return new PhoneNumber(value);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/FullName.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | @ValueObject
6 | public class FullName {
7 |
8 | private final String firstName;
9 | private final String lastName;
10 |
11 | private FullName(String firstName, String lastName) {
12 | this.firstName = firstName;
13 | this.lastName = lastName;
14 | }
15 |
16 | public String firstAndLastName() {
17 | return this.firstName + " " + this.lastName;
18 | }
19 |
20 | public static FullName fullName(String firstName, String lastName) {
21 | return new FullName(firstName, lastName);
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/test/java/com/github/cstettler/dddttc/rental/RentalApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental;
2 |
3 | import com.github.cstettler.dddttc.support.EnableComponentScanExclusions;
4 | import org.junit.jupiter.api.Test;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.test.context.ActiveProfiles;
8 | import org.springframework.test.context.junit.jupiter.SpringExtension;
9 |
10 | @ExtendWith(SpringExtension.class)
11 | @SpringBootTest
12 | @EnableComponentScanExclusions
13 | @ActiveProfiles("test")
14 | class RentalApplicationTests {
15 |
16 | @Test
17 | void bootstrappingApplicationContext_works() {
18 | // do nothing
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/UserRegistrationNotExistingException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class UserRegistrationNotExistingException extends RuntimeException {
7 |
8 | private UserRegistrationNotExistingException(UserRegistrationId userRegistrationId) {
9 | super("user registration '" + userRegistrationId.value() + "' does not exist");
10 | }
11 |
12 | public static UserRegistrationNotExistingException userRegistrationNotExisting(UserRegistrationId userRegistrationId) {
13 | return new UserRegistrationNotExistingException(userRegistrationId);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/infrastructure/sms/LoggingSmsNotificationSender.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.infrastructure.sms;
2 |
3 | import com.github.cstettler.dddttc.registration.domain.PhoneNumber;
4 | import com.github.cstettler.dddttc.registration.domain.SmsNotificationSender;
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | class LoggingSmsNotificationSender implements SmsNotificationSender {
9 |
10 | private static Logger LOGGER = LoggerFactory.getLogger(LoggingSmsNotificationSender.class);
11 |
12 | @Override
13 | public void sendSmsTo(PhoneNumber phoneNumber, String text) {
14 | LOGGER.info("SMS to '" + phoneNumber.value() + "': '" + text + "'");
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/test/java/com/github/cstettler/dddttc/accounting/AccountingApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting;
2 |
3 | import com.github.cstettler.dddttc.support.EnableComponentScanExclusions;
4 | import org.junit.jupiter.api.Test;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.test.context.ActiveProfiles;
8 | import org.springframework.test.context.junit.jupiter.SpringExtension;
9 |
10 | @ExtendWith(SpringExtension.class)
11 | @SpringBootTest
12 | @EnableComponentScanExclusions
13 | @ActiveProfiles("test")
14 | class AccountingApplicationTests {
15 |
16 | @Test
17 | void bootstrappingApplicationContext_works() {
18 | // do nothing
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/bike/BikeAlreadyBookedException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.bike;
2 |
3 | import com.github.cstettler.dddttc.rental.domain.user.UserId;
4 | import com.github.cstettler.dddttc.stereotype.BusinessException;
5 |
6 | @BusinessException
7 | public class BikeAlreadyBookedException extends RuntimeException {
8 |
9 | private BikeAlreadyBookedException(NumberPlate numberPlate, UserId userId) {
10 | super("bike '" + numberPlate.value() + "' is already booked by user '" + userId.value() + "'");
11 | }
12 |
13 | static BikeAlreadyBookedException bikeAlreadyBooked(NumberPlate numberPlate, UserId userId) {
14 | return new BikeAlreadyBookedException(numberPlate, userId);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/BookingId.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | import java.time.LocalDateTime;
6 |
7 | import static java.time.ZoneOffset.UTC;
8 |
9 | @ValueObject
10 | class BookingId {
11 |
12 | private final String value;
13 |
14 | private BookingId(WalletOwner walletOwner, LocalDateTime startedAt) {
15 | this.value = walletOwner.value() + "-" + startedAt.toEpochSecond(UTC);
16 | }
17 |
18 | String value() {
19 | return this.value;
20 | }
21 |
22 | static BookingId bookingId(WalletOwner walletOwner, LocalDateTime startedAt) {
23 | return new BookingId(walletOwner, startedAt);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/test/java/com/github/cstettler/dddttc/registration/RegistrationApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration;
2 |
3 | import com.github.cstettler.dddttc.support.EnableComponentScanExclusions;
4 | import org.junit.jupiter.api.Test;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.springframework.boot.test.context.SpringBootTest;
7 | import org.springframework.test.context.ActiveProfiles;
8 | import org.springframework.test.context.junit.jupiter.SpringExtension;
9 |
10 | @ExtendWith(SpringExtension.class)
11 | @SpringBootTest
12 | @EnableComponentScanExclusions
13 | @ActiveProfiles("test")
14 | class RegistrationApplicationTests {
15 |
16 | @Test
17 | void bootstrappingApplicationContext_works() {
18 | // do nothing
19 | }
20 |
21 | }
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/PhoneNumberVerificationCodeInvalidException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class PhoneNumberVerificationCodeInvalidException extends RuntimeException {
7 |
8 | private PhoneNumberVerificationCodeInvalidException(VerificationCode verificationCode) {
9 | super("phone number verification code '" + verificationCode.value() + "' is invalid");
10 | }
11 |
12 | static PhoneNumberVerificationCodeInvalidException phoneNumberVerificationCodeInvalid(VerificationCode verificationCode) {
13 | return new PhoneNumberVerificationCodeInvalidException(verificationCode);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/UserRegistrationAlreadyCompletedException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.BusinessException;
4 |
5 | @BusinessException
6 | public class UserRegistrationAlreadyCompletedException extends RuntimeException {
7 |
8 | private UserRegistrationAlreadyCompletedException(UserRegistrationId userRegistrationId) {
9 | super("user registration '" + userRegistrationId.value() + "' has already been completed");
10 | }
11 |
12 | static UserRegistrationAlreadyCompletedException userRegistrationAlreadyCompleted(UserRegistrationId userRegistrationId) {
13 | return new UserRegistrationAlreadyCompletedException(userRegistrationId);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/booking/BookingId.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.booking;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | import static java.util.UUID.randomUUID;
6 |
7 | @ValueObject
8 | public class BookingId {
9 |
10 | private String value;
11 |
12 | private BookingId() {
13 | this(randomUUID().toString());
14 | }
15 |
16 | private BookingId(String value) {
17 | this.value = value;
18 | }
19 |
20 | public String value() {
21 | return this.value;
22 | }
23 |
24 | public static BookingId bookingId(String value) {
25 | return new BookingId(value);
26 | }
27 |
28 | static BookingId newBookingId() {
29 | return new BookingId();
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-stereotype/src/main/java/com/github/cstettler/dddttc/stereotype/Repository.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.stereotype;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.Target;
6 |
7 | import static java.lang.annotation.ElementType.TYPE;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | /**
11 | * Represents a repository for aggregates. A repository is responsible for accepting aggregates of a specific type,
12 | * keeping them and returning the as requested by the domain. The repository interface forms part of the domain, the
13 | * implementation (e.g. against a database) is part of the infrastructure.
14 | */
15 | @Target(TYPE)
16 | @Retention(RUNTIME)
17 | @Documented
18 | public @interface Repository {
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/test/java/com/github/cstettler/dddttc/registration/domain/UserHandleTests.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import org.junit.jupiter.api.Test;
4 |
5 | import static com.github.cstettler.dddttc.registration.domain.UserHandle.userHandle;
6 | import static org.hamcrest.MatcherAssert.assertThat;
7 | import static org.hamcrest.Matchers.is;
8 |
9 | class UserHandleTests {
10 |
11 | @Test
12 | void equals_userHandlesWithSameValues_returnsTrue() {
13 | // arrange
14 | UserHandle userHandleOne = userHandle("peter");
15 | UserHandle userHandleTwo = userHandle("peter");
16 |
17 | // act + assert
18 | assertThat(userHandleOne.equals(userHandleTwo), is(true));
19 | assertThat(userHandleTwo.equals(userHandleOne), is(true));
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-stereotype/src/main/java/com/github/cstettler/dddttc/stereotype/InfrastructureService.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.stereotype;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.Target;
6 |
7 | import static java.lang.annotation.ElementType.TYPE;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | /**
11 | * Represents an infrastructure service. An infrastructure service provides functionality to the domain that requires
12 | * additional infrastructure only available outside of the domain. The infrastructure service interface forms part of
13 | * the domain, the implementation is part of the infrastructure.
14 | */
15 | @Target(TYPE)
16 | @Retention(RUNTIME)
17 | @Documented
18 | public @interface InfrastructureService {
19 | }
20 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/resources/templates/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Rental
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 | Error
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/Transaction.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | @ValueObject
6 | class Transaction {
7 |
8 | private final TransactionReference reference;
9 | private final Amount amount;
10 |
11 | private Transaction(TransactionReference reference, Amount amount) {
12 | this.reference = reference;
13 | this.amount = amount;
14 | }
15 |
16 | TransactionReference reference() {
17 | return this.reference;
18 | }
19 |
20 | Amount amount() {
21 | return this.amount;
22 | }
23 |
24 | static Transaction transaction(TransactionReference transactionReference, Amount amount) {
25 | return new Transaction(transactionReference, amount);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/TransactionWithSameReferenceAlreadyAppliedException.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | class TransactionWithSameReferenceAlreadyAppliedException extends RuntimeException {
4 |
5 | private TransactionWithSameReferenceAlreadyAppliedException(WalletOwner walletOwner, TransactionReference transactionReference) {
6 | super("transaction with reference '" + transactionReference.value() + " has already been applied to wallet '" + walletOwner.value() + "'");
7 | }
8 |
9 | static TransactionWithSameReferenceAlreadyAppliedException transactionWithSameReferenceAlreadyApplied(WalletOwner walletOwner, TransactionReference transactionReference) {
10 | return new TransactionWithSameReferenceAlreadyAppliedException(walletOwner, transactionReference);
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/resources/templates/error.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Registration
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 | Error
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/user/User.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.user;
2 |
3 | import com.github.cstettler.dddttc.stereotype.Aggregate;
4 | import com.github.cstettler.dddttc.stereotype.AggregateFactory;
5 |
6 | @Aggregate
7 | public class User {
8 |
9 | private final UserId id;
10 | private final FirstName firstName;
11 | private final LastName lastName;
12 |
13 | private User(UserId id, FirstName firstName, LastName lastName) {
14 | this.id = id;
15 | this.firstName = firstName;
16 | this.lastName = lastName;
17 | }
18 |
19 | public UserId id() {
20 | return this.id;
21 | }
22 |
23 | @AggregateFactory(User.class)
24 | public static User newUser(UserId userId, FirstName firstName, LastName lastName) {
25 | return new User(userId, firstName, lastName);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/UserRegistrationId.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | import static java.util.UUID.randomUUID;
6 |
7 | @ValueObject
8 | public class UserRegistrationId {
9 |
10 | private final String value;
11 |
12 | private UserRegistrationId() {
13 | this(randomUUID().toString());
14 | }
15 |
16 | private UserRegistrationId(String value) {
17 | this.value = value;
18 | }
19 |
20 | public String value() {
21 | return this.value;
22 | }
23 |
24 | public static UserRegistrationId userRegistrationId(String value) {
25 | return new UserRegistrationId(value);
26 | }
27 |
28 | static UserRegistrationId newUserRegistrationId() {
29 | return new UserRegistrationId();
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/Amount.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | import java.math.BigDecimal;
6 |
7 | import static java.math.BigDecimal.ZERO;
8 |
9 | @ValueObject
10 | public class Amount {
11 |
12 | private final BigDecimal value;
13 |
14 | private Amount(BigDecimal value) {
15 | this.value = value;
16 | }
17 |
18 | public BigDecimal value() {
19 | return this.value;
20 | }
21 |
22 | Amount negate() {
23 | return amount(this.value.negate());
24 | }
25 |
26 | Amount add(Amount other) {
27 | return amount(this.value.add(other.value));
28 | }
29 |
30 | static Amount zero() {
31 | return amount(ZERO);
32 | }
33 |
34 | static Amount amount(BigDecimal value) {
35 | return new Amount(value);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-stereotype/src/main/java/com/github/cstettler/dddttc/stereotype/DomainEventHandler.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.stereotype;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.Target;
6 |
7 | import static java.lang.annotation.ElementType.METHOD;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | /**
11 | * Represents a handler of a domain event. A domain event handler consumes domain events of a specific type. Domain
12 | * event handlers are responsible for dealing with receiving the same domain event multiple times (at-least-once
13 | * semantics).
14 | *
15 | * Domain event handlers are typically part of domain services. A domain event handler method must accept a single
16 | * parameter of the domain event type handled.
17 | */
18 | @Target(METHOD)
19 | @Retention(RUNTIME)
20 | @Documented
21 | public @interface DomainEventHandler {
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-support/src/main/java/com/github/cstettler/dddttc/support/infrastructure/event/PendingDomainEvent.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.support.infrastructure.event;
2 |
3 | import java.time.LocalDateTime;
4 |
5 | class PendingDomainEvent {
6 |
7 | private final String id;
8 | private final String type;
9 | private final String payload;
10 | private final LocalDateTime publishedAt;
11 |
12 | PendingDomainEvent(String id, String type, String payload, LocalDateTime publishedAt) {
13 | this.id = id;
14 | this.type = type;
15 | this.payload = payload;
16 | this.publishedAt = publishedAt;
17 | }
18 |
19 | String id() {
20 | return this.id;
21 | }
22 |
23 | String type() {
24 | return this.type;
25 | }
26 |
27 | String payload() {
28 | return this.payload;
29 | }
30 |
31 | LocalDateTime publishedAt() {
32 | return this.publishedAt;
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/test/java/com/github/cstettler/dddttc/accounting/domain/WalletBuilder.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.support.ReflectionBasedStateBuilder;
4 |
5 | @SuppressWarnings("UnusedReturnValue")
6 | public class WalletBuilder extends ReflectionBasedStateBuilder {
7 |
8 | private WalletBuilder() {
9 | walletOwner(anyWalletOwner());
10 | }
11 |
12 | public WalletBuilder walletOwner(String idValue) {
13 | return walletOwner(WalletOwner.walletOwner(idValue));
14 | }
15 |
16 | public WalletBuilder walletOwner(WalletOwner walletOwner) {
17 | return recordProperty(this, "walletOwner", walletOwner);
18 | }
19 |
20 | public static WalletBuilder wallet() {
21 | return new WalletBuilder();
22 | }
23 |
24 | private static WalletOwner anyWalletOwner() {
25 | return WalletOwner.walletOwner(randomString(10));
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-support/src/main/java/com/github/cstettler/dddttc/support/infrastructure/event/DomainEventTypeResolver.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.support.infrastructure.event;
2 |
3 | import java.util.List;
4 |
5 | class DomainEventTypeResolver {
6 |
7 | private final List allDomainEventTypeMappings;
8 |
9 | DomainEventTypeResolver(List allDomainEventTypeMappings) {
10 | this.allDomainEventTypeMappings = allDomainEventTypeMappings;
11 | }
12 |
13 | String resolveDomainEventType(Class> domainEventClass) {
14 | for (DomainEventTypeMappings domainEventTypeMappings : this.allDomainEventTypeMappings) {
15 | String domainEventType = domainEventTypeMappings.getDomainEventTypeIfAvailable(domainEventClass);
16 |
17 | if (domainEventType != null) {
18 | return domainEventType;
19 | }
20 | }
21 |
22 | return domainEventClass.getName();
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-support/src/test/java/com/github/cstettler/dddttc/support/infrastructure/event/TestDomainService.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.support.infrastructure.event;
2 |
3 | import com.github.cstettler.dddttc.stereotype.DomainEventHandler;
4 | import com.github.cstettler.dddttc.stereotype.DomainService;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | @DomainService
10 | class TestDomainService {
11 |
12 | private List recordedDomainEvents;
13 |
14 | TestDomainService() {
15 | this.recordedDomainEvents = new ArrayList<>();
16 | }
17 |
18 | @DomainEventHandler
19 | void handleTestDomainEventWithImplicitType(TestDomainEvent event) {
20 | this.recordedDomainEvents.add(event);
21 | }
22 |
23 | List recordedDomainEvents() {
24 | return this.recordedDomainEvents;
25 | }
26 |
27 | void clearRecordedDomainEvents() {
28 | this.recordedDomainEvents.clear();
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/bike/ReleaseBikeService.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.bike;
2 |
3 | import com.github.cstettler.dddttc.rental.domain.booking.BookingCompletedEvent;
4 | import com.github.cstettler.dddttc.rental.domain.user.UserId;
5 | import com.github.cstettler.dddttc.stereotype.DomainEventHandler;
6 | import com.github.cstettler.dddttc.stereotype.DomainService;
7 |
8 | @DomainService
9 | class ReleaseBikeService {
10 |
11 | private final BikeRepository bikeRepository;
12 |
13 | ReleaseBikeService(BikeRepository bikeRepository) {
14 | this.bikeRepository = bikeRepository;
15 | }
16 |
17 | @DomainEventHandler
18 | void releaseBike(BookingCompletedEvent event) {
19 | NumberPlate numberPlate = event.numberPlate();
20 | UserId lastUserId = event.userId();
21 |
22 | Bike bike = this.bikeRepository.get(numberPlate);
23 | bike.markAsReturnedBy(lastUserId);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/BookingFeePolicy.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.DomainService;
4 |
5 | import java.math.BigDecimal;
6 |
7 | import static com.github.cstettler.dddttc.accounting.domain.Amount.amount;
8 | import static java.lang.Math.round;
9 | import static java.math.BigDecimal.valueOf;
10 |
11 | @DomainService
12 | public class BookingFeePolicy {
13 |
14 | private final BigDecimal initialPrice;
15 | private final BigDecimal pricePerMinute;
16 |
17 | BookingFeePolicy() {
18 | this.initialPrice = new BigDecimal("1.50");
19 | this.pricePerMinute = new BigDecimal("0.25");
20 | }
21 |
22 | Amount feeForBooking(Booking booking) {
23 | int roundedMinutes = round((float) booking.durationInSeconds() / 60);
24 |
25 | return amount(this.initialPrice.add(this.pricePerMinute.multiply(valueOf(roundedMinutes))));
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-stereotype/src/main/java/com/github/cstettler/dddttc/stereotype/AggregateFactory.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.stereotype;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.Target;
6 |
7 | import static java.lang.annotation.ElementType.METHOD;
8 | import static java.lang.annotation.ElementType.TYPE;
9 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
10 |
11 | /**
12 | * Represents a factory responsible for creating new aggregate instances. An aggregate factory encapsulates the
13 | * knowledge required to create a new aggregate instance. An aggregate factory can either be a static method on an
14 | * aggregate, or a separate class, depending on the complexity of the instantiation process and the dependencies needed.
15 | */
16 | @Target({TYPE, METHOD})
17 | @Retention(RUNTIME)
18 | @Documented
19 | public @interface AggregateFactory {
20 |
21 | /**
22 | * The type of aggregate created
23 | */
24 | Class> value();
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/SendVerificationCodeSmsService.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.DomainEventHandler;
4 | import com.github.cstettler.dddttc.stereotype.DomainService;
5 |
6 | @DomainService
7 | class SendVerificationCodeSmsService {
8 |
9 | private final SmsNotificationSender smsNotificationSender;
10 |
11 | SendVerificationCodeSmsService(SmsNotificationSender smsNotificationSender) {
12 | this.smsNotificationSender = smsNotificationSender;
13 | }
14 |
15 | @DomainEventHandler
16 | void sendVerificationCodeSmsToPhoneNumber(PhoneNumberVerificationCodeGeneratedEvent event) {
17 | PhoneNumber phoneNumber = event.phoneNumber();
18 | VerificationCode verificationCode = event.verificationCode();
19 |
20 | String smsText = "Your verification code is " + verificationCode.value();
21 |
22 | this.smsNotificationSender.sendSmsTo(phoneNumber, smsText);
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/application/BikeService.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.application;
2 |
3 | import com.github.cstettler.dddttc.rental.domain.bike.Bike;
4 | import com.github.cstettler.dddttc.rental.domain.bike.BikeNotExistingException;
5 | import com.github.cstettler.dddttc.rental.domain.bike.BikeRepository;
6 | import com.github.cstettler.dddttc.rental.domain.bike.NumberPlate;
7 | import com.github.cstettler.dddttc.stereotype.ApplicationService;
8 |
9 | import java.util.Collection;
10 |
11 | @ApplicationService
12 | public class BikeService {
13 |
14 | private final BikeRepository bikeRepository;
15 |
16 | BikeService(BikeRepository bikeRepository) {
17 | this.bikeRepository = bikeRepository;
18 | }
19 |
20 | public Collection listBikes() {
21 | return this.bikeRepository.findAll();
22 | }
23 |
24 | public Bike getBike(NumberPlate numberPlate) throws BikeNotExistingException {
25 | return this.bikeRepository.get(numberPlate);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/test/java/com/github/cstettler/dddttc/accounting/domain/WalletMatcher.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.support.ReflectionBasedStateMatcher;
4 | import org.hamcrest.Matcher;
5 |
6 | import java.util.Map;
7 |
8 | public class WalletMatcher extends ReflectionBasedStateMatcher {
9 |
10 | private WalletMatcher(Map> matchersByPropertyName) {
11 | super("wallet", matchersByPropertyName);
12 | }
13 |
14 | public static Builder walletWith() {
15 | return new Builder();
16 | }
17 |
18 |
19 | public static class Builder extends MatcherBuilder {
20 |
21 | public Builder walletOwner(Matcher> idMatcher) {
22 | return recordPropertyMatcher(this, "walletOwner", idMatcher);
23 | }
24 |
25 | @Override
26 | protected WalletMatcher build(Map> matchersByPropertyName) {
27 | return new WalletMatcher(matchersByPropertyName);
28 | }
29 |
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/booking/BikeUsage.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.booking;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | import java.time.LocalDateTime;
6 |
7 | import static java.time.ZoneOffset.UTC;
8 |
9 | @ValueObject
10 | public class BikeUsage {
11 |
12 | private final LocalDateTime startedAt;
13 | private final LocalDateTime endedAt;
14 | private final long durationInSeconds;
15 |
16 | private BikeUsage(LocalDateTime startedAt, LocalDateTime endedAt) {
17 | this.startedAt = startedAt;
18 | this.endedAt = endedAt;
19 | this.durationInSeconds = usageTimeInSeconds(startedAt, endedAt);
20 | }
21 |
22 | private long usageTimeInSeconds(LocalDateTime startedAt, LocalDateTime endedAt) {
23 | return endedAt.toEpochSecond(UTC) - startedAt.toEpochSecond(UTC);
24 | }
25 |
26 | static BikeUsage bikeUsage(LocalDateTime startedAt, LocalDateTime endedAt) {
27 | return new BikeUsage(startedAt, endedAt);
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-support/src/test/java/com/github/cstettler/dddttc/support/test/ScenarioTest.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.support.test;
2 |
3 |
4 | import com.github.cstettler.dddttc.support.EnableComponentScanExclusions;
5 | import org.junit.jupiter.api.extension.ExtendWith;
6 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
7 | import org.springframework.boot.test.context.SpringBootTest;
8 | import org.springframework.test.context.ActiveProfiles;
9 | import org.springframework.test.context.junit.jupiter.SpringExtension;
10 |
11 | import java.lang.annotation.Retention;
12 | import java.lang.annotation.Target;
13 |
14 | import static java.lang.annotation.ElementType.TYPE;
15 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
16 |
17 | @Target(TYPE)
18 | @Retention(RUNTIME)
19 | @ExtendWith(SpringExtension.class)
20 | @ExtendWith(DatabaseCleaner.class)
21 | @SpringBootTest
22 | @WithDomainEventTestSupport
23 | @AutoConfigureTestDatabase
24 | @EnableComponentScanExclusions
25 | @ActiveProfiles("test")
26 | public @interface ScenarioTest {
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/PhoneNumberVerificationCodeGeneratedEvent.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.DomainEvent;
4 |
5 | @DomainEvent
6 | public class PhoneNumberVerificationCodeGeneratedEvent {
7 |
8 | private final PhoneNumber phoneNumber;
9 | private final VerificationCode verificationCode;
10 |
11 | private PhoneNumberVerificationCodeGeneratedEvent(PhoneNumber phoneNumber, VerificationCode verificationCode) {
12 | this.phoneNumber = phoneNumber;
13 | this.verificationCode = verificationCode;
14 | }
15 |
16 | public PhoneNumber phoneNumber() {
17 | return this.phoneNumber;
18 | }
19 |
20 | public VerificationCode verificationCode() {
21 | return this.verificationCode;
22 | }
23 |
24 | static PhoneNumberVerificationCodeGeneratedEvent phoneNumberVerificationCodeGenerated(PhoneNumber phoneNumber, VerificationCode verificationCode) {
25 | return new PhoneNumberVerificationCodeGeneratedEvent(phoneNumber, verificationCode);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-stereotype/src/main/java/com/github/cstettler/dddttc/stereotype/DomainEvent.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.stereotype;
2 |
3 | import java.lang.annotation.Documented;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.Target;
6 |
7 | import static java.lang.annotation.ElementType.TYPE;
8 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
9 |
10 | /**
11 | * Represents an event relevant to the business domain. A domain event describes a fact that has happened in the past.
12 | * Domain events are immutable and carry the state relevant for consumers to understand the semantics of the domain
13 | * event.
14 | */
15 | @Target(TYPE)
16 | @Retention(RUNTIME)
17 | @Documented
18 | public @interface DomainEvent {
19 |
20 | /**
21 | * Type of the domain event. Must be defined for cross-bounded context domain events, but are is not mandatory for
22 | * intra-bounded context domain events. If defined, the type must be unique within the whole system. If not defined,
23 | * the type of the domain event is derived from the domain event class.
24 | */
25 | String value() default "";
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/application/UserService.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.application;
2 |
3 | import com.github.cstettler.dddttc.rental.domain.user.FirstName;
4 | import com.github.cstettler.dddttc.rental.domain.user.LastName;
5 | import com.github.cstettler.dddttc.rental.domain.user.User;
6 | import com.github.cstettler.dddttc.rental.domain.user.UserAlreadyExistsException;
7 | import com.github.cstettler.dddttc.rental.domain.user.UserId;
8 | import com.github.cstettler.dddttc.rental.domain.user.UserRepository;
9 | import com.github.cstettler.dddttc.stereotype.ApplicationService;
10 |
11 | import static com.github.cstettler.dddttc.rental.domain.user.User.newUser;
12 |
13 | @ApplicationService
14 | public class UserService {
15 |
16 | private final UserRepository userRepository;
17 |
18 | UserService(UserRepository userRepository) {
19 | this.userRepository = userRepository;
20 | }
21 |
22 | public void addUser(UserId userId, FirstName firstName, LastName lastName) throws UserAlreadyExistsException {
23 | User user = newUser(userId, firstName, lastName);
24 |
25 | this.userRepository.add(user);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/domain/bike/InitializeBikesService.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.domain.bike;
2 |
3 | import com.github.cstettler.dddttc.stereotype.DomainService;
4 | import com.github.cstettler.dddttc.support.domain.DomainEventPublisher;
5 |
6 | import static com.github.cstettler.dddttc.rental.domain.bike.Bike.newBike;
7 | import static com.github.cstettler.dddttc.rental.domain.bike.NumberPlate.numberPlate;
8 |
9 | @DomainService
10 | public class InitializeBikesService {
11 |
12 | private final BikeRepository bikeRepository;
13 | private final DomainEventPublisher domainEventPublisher;
14 |
15 | InitializeBikesService(BikeRepository bikeRepository, DomainEventPublisher domainEventPublisher) {
16 | this.bikeRepository = bikeRepository;
17 | this.domainEventPublisher = domainEventPublisher;
18 | }
19 |
20 | public void initializeBikes() {
21 | this.bikeRepository.add(newBike(numberPlate("ZH-123"), this.domainEventPublisher));
22 | this.bikeRepository.add(newBike(numberPlate("ZH-987"), this.domainEventPublisher));
23 | this.bikeRepository.add(newBike(numberPlate("ZH-666"), this.domainEventPublisher));
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/infrastructure/bootstrap/BikeInitializationTrigger.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.infrastructure.bootstrap;
2 |
3 | import com.github.cstettler.dddttc.rental.domain.bike.InitializeBikesService;
4 | import org.springframework.stereotype.Component;
5 | import org.springframework.transaction.PlatformTransactionManager;
6 | import org.springframework.transaction.support.TransactionTemplate;
7 |
8 | import javax.annotation.PostConstruct;
9 |
10 | @Component
11 | class BikeInitializationTrigger {
12 |
13 | private final InitializeBikesService initializeBikesService;
14 | private final TransactionTemplate transactionTemplate;
15 |
16 | BikeInitializationTrigger(InitializeBikesService initializeBikesService, PlatformTransactionManager transactionManager) {
17 | this.initializeBikesService = initializeBikesService;
18 | this.transactionTemplate = new TransactionTemplate(transactionManager);
19 | }
20 |
21 | @PostConstruct
22 | void triggerBikeInitialization() {
23 | this.transactionTemplate.execute((status) -> {
24 | this.initializeBikesService.initializeBikes();
25 |
26 | return null;
27 | });
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-aspect/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 |
7 | com.github.cstettler.dddttc
8 | ddd-to-the-code-workshop
9 | 0.0.1-SNAPSHOT
10 |
11 |
12 | ddd-to-the-code-workshop-aspect
13 | jar
14 |
15 |
16 |
17 | com.github.cstettler.dddttc
18 | ddd-to-the-code-workshop-stereotype
19 | ${project.version}
20 | compile
21 |
22 |
23 |
24 | com.google.auto.service
25 | auto-service
26 | 1.0-rc3
27 | compile
28 | true
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/java/com/github/cstettler/dddttc/accounting/domain/Booking.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.accounting.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | import java.time.LocalDateTime;
6 |
7 | import static com.github.cstettler.dddttc.accounting.domain.BookingId.bookingId;
8 |
9 | @ValueObject
10 | public class Booking {
11 |
12 | private final BookingId id;
13 | private final WalletOwner walletOwner;
14 | private final long durationInSeconds;
15 |
16 | private Booking(WalletOwner walletOwner, LocalDateTime startedAt, long durationInSeconds) {
17 | this.id = bookingId(walletOwner, startedAt);
18 | this.walletOwner = walletOwner;
19 | this.durationInSeconds = durationInSeconds;
20 | }
21 |
22 | public BookingId id() {
23 | return this.id;
24 | }
25 |
26 | public WalletOwner walletOwner() {
27 | return this.walletOwner;
28 | }
29 |
30 | public long durationInSeconds() {
31 | return this.durationInSeconds;
32 | }
33 |
34 | public static Booking booking(WalletOwner walletOwner, LocalDateTime startedAt, long durationInSeconds) {
35 | return new Booking(walletOwner, startedAt, durationInSeconds);
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-support/src/main/java/com/github/cstettler/dddttc/support/infrastructure/web/ModelAndViewBuilder.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.support.infrastructure.web;
2 |
3 | import org.springframework.web.servlet.ModelAndView;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | public class ModelAndViewBuilder {
9 |
10 | private final Map modelProperties;
11 | private final String viewName;
12 |
13 | private ModelAndViewBuilder(String viewName) {
14 | this.viewName = viewName;
15 | this.modelProperties = new HashMap<>();
16 | }
17 |
18 | public ModelAndViewBuilder property(String name, Object value) {
19 | this.modelProperties.put(name, value);
20 |
21 | return this;
22 | }
23 |
24 | public ModelAndViewBuilder error(Exception e) {
25 | this.modelProperties.put("error", e.getMessage());
26 |
27 | return this;
28 | }
29 |
30 | public ModelAndView build() {
31 | return new ModelAndView(this.viewName, this.modelProperties);
32 | }
33 |
34 | public static ModelAndViewBuilder modelAndView(String viewName) {
35 | return new ModelAndViewBuilder(viewName);
36 | }
37 |
38 | public static ModelAndView redirectTo(String url) {
39 | return modelAndView("redirect:" + url).build();
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-support/src/main/java/com/github/cstettler/dddttc/support/infrastructure/event/DomainEventTypeMappings.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.support.infrastructure.event;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import static java.util.Collections.unmodifiableMap;
7 |
8 | public class DomainEventTypeMappings {
9 |
10 | private final Map, String> mappings;
11 |
12 | private DomainEventTypeMappings(Map, String> mappings) {
13 | this.mappings = unmodifiableMap(mappings);
14 | }
15 |
16 | String getDomainEventTypeIfAvailable(Class> domainEventClass) {
17 | return this.mappings.get(domainEventClass);
18 | }
19 |
20 | public static Builder domainEventTypeMappings() {
21 | return new Builder();
22 | }
23 |
24 |
25 | public static class Builder {
26 |
27 | private final Map, String> mappings;
28 |
29 | private Builder() {
30 | this.mappings = new HashMap<>();
31 | }
32 |
33 | public Builder addMapping(Class> domainEventClass, String domainEventType) {
34 | this.mappings.put(domainEventClass, domainEventType);
35 |
36 | return this;
37 | }
38 |
39 | public DomainEventTypeMappings build() {
40 | return new DomainEventTypeMappings(this.mappings);
41 | }
42 |
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-rental/src/main/java/com/github/cstettler/dddttc/rental/infrastructure/persistence/InMemoryBikeRepository.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.rental.infrastructure.persistence;
2 |
3 | import com.github.cstettler.dddttc.rental.domain.bike.Bike;
4 | import com.github.cstettler.dddttc.rental.domain.bike.BikeRepository;
5 | import com.github.cstettler.dddttc.rental.domain.bike.NumberPlate;
6 |
7 | import java.util.Collection;
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | import static com.github.cstettler.dddttc.rental.domain.bike.BikeNotExistingException.bikeNotExisting;
12 |
13 | class InMemoryBikeRepository implements BikeRepository {
14 |
15 | private final Map bikeByNumberPlate;
16 |
17 | InMemoryBikeRepository() {
18 | this.bikeByNumberPlate = new HashMap<>();
19 | }
20 |
21 | @Override
22 | public void add(Bike bike) {
23 | this.bikeByNumberPlate.put(bike.numberPlate(), bike);
24 | }
25 |
26 | @Override
27 | public Bike get(NumberPlate numberPlate) {
28 | Bike bike = this.bikeByNumberPlate.get(numberPlate);
29 |
30 | if (bike == null) {
31 | throw bikeNotExisting(numberPlate);
32 | }
33 |
34 | return bike;
35 | }
36 |
37 | @Override
38 | public Collection findAll() {
39 | return this.bikeByNumberPlate.values();
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-accounting/src/main/resources/templates/list-wallets.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Accounting
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 | Wallets and Balances
18 |
19 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-registration/src/main/java/com/github/cstettler/dddttc/registration/domain/VerificationCode.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.registration.domain;
2 |
3 | import com.github.cstettler.dddttc.stereotype.ValueObject;
4 |
5 | import static java.lang.String.valueOf;
6 | import static java.util.stream.Collectors.joining;
7 | import static java.util.stream.IntStream.range;
8 |
9 | @ValueObject
10 | public class VerificationCode {
11 |
12 | private final String value;
13 |
14 | private VerificationCode() {
15 | this(pseudoRandomNumericString(6));
16 | }
17 |
18 | private VerificationCode(String value) {
19 | this.value = value;
20 | }
21 |
22 | String value() {
23 | return this.value;
24 | }
25 |
26 | boolean matches(VerificationCode verificationCode) {
27 | return this.value.equals(verificationCode.value());
28 | }
29 |
30 | public static VerificationCode verificationCode(String value) {
31 | return new VerificationCode(value);
32 | }
33 |
34 | static VerificationCode randomVerificationCode() {
35 | return new VerificationCode();
36 | }
37 |
38 | private static String pseudoRandomNumericString(int length) {
39 | return range(0, length)
40 | .map((i) -> (int) (Math.random() * 10 % 10))
41 | .mapToObj((number) -> valueOf(number))
42 | .collect(joining());
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/ddd-to-the-code-workshop-support/src/test/java/com/github/cstettler/dddttc/support/domain/RecordingDomainEventPublisher.java:
--------------------------------------------------------------------------------
1 | package com.github.cstettler.dddttc.support.domain;
2 |
3 | import com.github.cstettler.dddttc.support.EnableComponentScanExclusions.ExcludeFromComponentScan;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | @ExcludeFromComponentScan
9 | public class RecordingDomainEventPublisher implements DomainEventPublisher {
10 |
11 | private final List