Skip to content

Commit 8d7db2c

Browse files
committed
refactor(subscription): enforce lifecycle invariants, unify exception naming, move AggregateRoot to domain.model
1 parent 95d5bba commit 8d7db2c

12 files changed

Lines changed: 88 additions & 35 deletions

File tree

order-bootstrap/src/main/java/dev/imdmk/ordersystem/bootstrap/subscription/SubscriptionExpirationJob.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public SubscriptionExpirationJob(
1818
this.expirationService = Objects.requireNonNull(expirationService, "expirationService must not be null");
1919
}
2020

21-
@Scheduled(cron = "*/10 * * * * *")
21+
@Scheduled(cron = "0 */5 * * * *")
2222
public void run() {
2323
expirationService.expireDueSubscriptions(Instant.now());
2424
}

order-domain/src/main/java/dev/imdmk/ordersystem/domain/common/AggregateRoot.java renamed to order-domain/src/main/java/dev/imdmk/ordersystem/domain/model/AggregateRoot.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package dev.imdmk.ordersystem.domain.common;
1+
package dev.imdmk.ordersystem.domain.model;
22

33
import dev.imdmk.ordersystem.domain.event.DomainEvent;
44

@@ -8,7 +8,7 @@
88

99
public abstract class AggregateRoot {
1010

11-
private final List<DomainEvent> domainEvents = new ArrayList<>();
11+
private final List<DomainEvent> domainEvents = new ArrayList<>(4);
1212

1313
protected void registerEvent(DomainEvent event) {
1414
Objects.requireNonNull(event, "event must not be null");

order-domain/src/main/java/dev/imdmk/ordersystem/domain/order/Order.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package dev.imdmk.ordersystem.domain.order;
22

3-
import dev.imdmk.ordersystem.domain.common.AggregateRoot;
3+
import dev.imdmk.ordersystem.domain.model.AggregateRoot;
44
import dev.imdmk.ordersystem.domain.order.event.OrderCancelEvent;
55
import dev.imdmk.ordersystem.domain.order.event.OrderPaidEvent;
66
import dev.imdmk.ordersystem.domain.order.state.CancelledOrder;

order-domain/src/main/java/dev/imdmk/ordersystem/domain/subscription/Subscription.java

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,70 @@
11
package dev.imdmk.ordersystem.domain.subscription;
22

3-
import dev.imdmk.ordersystem.domain.common.AggregateRoot;
3+
import dev.imdmk.ordersystem.domain.model.AggregateRoot;
44
import dev.imdmk.ordersystem.domain.order.OrderId;
55
import dev.imdmk.ordersystem.domain.subscription.event.SubscriptionCancelledEvent;
66
import dev.imdmk.ordersystem.domain.subscription.event.SubscriptionExpiredEvent;
77
import dev.imdmk.ordersystem.domain.subscription.event.SubscriptionStartedEvent;
8+
import dev.imdmk.ordersystem.domain.subscription.exception.CannotCancelExpiredSubscriptionException;
9+
import dev.imdmk.ordersystem.domain.subscription.exception.CannotExpireCancelledSubscriptionException;
810
import dev.imdmk.ordersystem.domain.subscription.exception.CannotExpirePermanentSubscriptionException;
911
import dev.imdmk.ordersystem.domain.subscription.exception.SubscriptionAlreadyCancelledException;
1012
import dev.imdmk.ordersystem.domain.subscription.exception.SubscriptionAlreadyExpiredException;
13+
import dev.imdmk.ordersystem.domain.subscription.exception.SubscriptionNotYetExpiredException;
1114

1215
import java.util.Objects;
1316

1417
public final class Subscription extends AggregateRoot {
1518

1619
private final SubscriptionId id;
1720
private final OrderId orderId;
18-
private SubscriptionStatus status;
1921
private final Expiration expiration;
22+
private SubscriptionStatus status;
2023

2124
private Subscription(
2225
SubscriptionId id,
2326
OrderId orderId,
24-
SubscriptionStatus status,
25-
Expiration expiration
27+
Expiration expiration,
28+
SubscriptionStatus status
2629
) {
2730
this.id = Objects.requireNonNull(id, "id must not be null");
2831
this.orderId = Objects.requireNonNull(orderId, "orderId must not be null");
29-
this.status = Objects.requireNonNull(status, "status must not be null");
3032
this.expiration = Objects.requireNonNull(expiration, "expiration must not be null");
33+
this.status = Objects.requireNonNull(status, "status must not be null");
3134
}
3235

33-
public static Subscription from(SubscriptionId id, OrderId orderId, SubscriptionStatus status, Expiration expiration) {
34-
return new Subscription(id, orderId, status, expiration);
36+
public static Subscription from(
37+
SubscriptionId id,
38+
OrderId orderId,
39+
Expiration expiration,
40+
SubscriptionStatus status
41+
) {
42+
return new Subscription(id, orderId, expiration, status);
3543
}
3644

3745
public static Subscription start(
3846
OrderId orderId,
3947
Expiration expiration
4048
) {
4149
final SubscriptionId subscriptionId = SubscriptionId.newId();
42-
final Subscription subscription = from(subscriptionId, orderId, SubscriptionStatus.ACTIVE, expiration);
50+
final Subscription subscription = from(subscriptionId, orderId, expiration, SubscriptionStatus.ACTIVE);
51+
52+
subscription.registerEvent(
53+
SubscriptionStartedEvent.now(subscription.id, subscription.orderId)
54+
);
4355

44-
subscription.registerEvent(SubscriptionStartedEvent.now(subscription.id, subscription.orderId));
4556
return subscription;
4657
}
4758

4859
public void cancel() {
49-
if (isCancelled()) {
60+
if (status == SubscriptionStatus.CANCELLED) {
5061
throw new SubscriptionAlreadyCancelledException(id);
5162
}
5263

64+
if (status == SubscriptionStatus.EXPIRED) {
65+
throw new CannotCancelExpiredSubscriptionException(id);
66+
}
67+
5368
this.status = SubscriptionStatus.CANCELLED;
5469
registerEvent(SubscriptionCancelledEvent.now(id, orderId));
5570
}
@@ -59,22 +74,42 @@ public void expire() {
5974
throw new SubscriptionAlreadyExpiredException(id);
6075
}
6176

77+
if (status == SubscriptionStatus.CANCELLED) {
78+
throw new CannotExpireCancelledSubscriptionException(id);
79+
}
80+
6281
if (expiration.isPermanent()) {
6382
throw new CannotExpirePermanentSubscriptionException(id);
6483
}
6584

85+
if (!expiration.isExpired()) {
86+
throw new SubscriptionNotYetExpiredException(id);
87+
}
88+
6689
this.status = SubscriptionStatus.EXPIRED;
6790
registerEvent(SubscriptionExpiredEvent.now(id, orderId));
6891
}
6992

7093
public boolean isActive() {
71-
return status == SubscriptionStatus.ACTIVE && !expiration.isExpired();
94+
if (status != SubscriptionStatus.ACTIVE) {
95+
return false;
96+
}
97+
98+
if (expiration.isPermanent()) {
99+
return true;
100+
}
101+
102+
return !expiration.isExpired();
72103
}
73104

74105
public boolean isCancelled() {
75106
return status == SubscriptionStatus.CANCELLED;
76107
}
77108

109+
public boolean isExpired() {
110+
return status == SubscriptionStatus.EXPIRED;
111+
}
112+
78113
public SubscriptionId getId() {
79114
return id;
80115
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.imdmk.ordersystem.domain.subscription.exception;
2+
3+
import dev.imdmk.ordersystem.domain.subscription.SubscriptionId;
4+
5+
public class CannotCancelExpiredSubscriptionException extends RuntimeException {
6+
public CannotCancelExpiredSubscriptionException(SubscriptionId id) {
7+
super("Cannot cancel expired subscription: " + id);
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package dev.imdmk.ordersystem.domain.subscription.exception;
2+
3+
import dev.imdmk.ordersystem.domain.subscription.SubscriptionId;
4+
5+
public class CannotExpireCancelledSubscriptionException extends RuntimeException {
6+
public CannotExpireCancelledSubscriptionException(SubscriptionId id) {
7+
super("Cannot expire cancelled subscription: " + id);
8+
}
9+
}

order-domain/src/main/java/dev/imdmk/ordersystem/domain/subscription/exception/CannotExpirePermanentSubscriptionException.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import dev.imdmk.ordersystem.domain.subscription.SubscriptionId;
44

5-
public final class CannotExpirePermanentSubscriptionException extends RuntimeException {
6-
public CannotExpirePermanentSubscriptionException(SubscriptionId subscriptionId) {
7-
super("Permanent subscription with id %s cannot expire".formatted(subscriptionId));
5+
public class CannotExpirePermanentSubscriptionException extends RuntimeException {
6+
public CannotExpirePermanentSubscriptionException(SubscriptionId id) {
7+
super("Cannot expire permanent subscription: " + id);
88
}
99
}

order-domain/src/main/java/dev/imdmk/ordersystem/domain/subscription/exception/SubscriptionAlreadyActiveException.java

Lines changed: 0 additions & 9 deletions
This file was deleted.

order-domain/src/main/java/dev/imdmk/ordersystem/domain/subscription/exception/SubscriptionAlreadyCancelledException.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import dev.imdmk.ordersystem.domain.subscription.SubscriptionId;
44

5-
public final class SubscriptionAlreadyCancelledException extends RuntimeException {
6-
public SubscriptionAlreadyCancelledException(SubscriptionId subscriptionId) {
7-
super("Subscription with id %s is already cancelled".formatted(subscriptionId));
5+
public class SubscriptionAlreadyCancelledException extends RuntimeException {
6+
public SubscriptionAlreadyCancelledException(SubscriptionId id) {
7+
super("Subscription already cancelled: " + id);
88
}
99
}

order-domain/src/main/java/dev/imdmk/ordersystem/domain/subscription/exception/SubscriptionAlreadyExpiredException.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
import dev.imdmk.ordersystem.domain.subscription.SubscriptionId;
44

5-
public final class SubscriptionAlreadyExpiredException extends RuntimeException {
6-
public SubscriptionAlreadyExpiredException(SubscriptionId subscriptionId) {
7-
super("Subscription with id %s already expired".formatted(subscriptionId));
5+
public class SubscriptionAlreadyExpiredException extends RuntimeException {
6+
public SubscriptionAlreadyExpiredException(SubscriptionId id) {
7+
super("Subscription already expired: " + id);
88
}
99
}

0 commit comments

Comments
 (0)