Skip to content

Commit f777e39

Browse files
authored
feat: add kafka to award-service (#7)
1 parent 78b81fb commit f777e39

21 files changed

Lines changed: 912 additions & 30 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
1+
.cursor
2+
**/.cursor
23
**/.claude/settings.local.json
34
.cursor

sticker-award/pom.xml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,40 @@
6868
<groupId>io.quarkus</groupId>
6969
<artifactId>quarkus-rest-jackson</artifactId>
7070
</dependency>
71+
<dependency>
72+
<groupId>io.quarkus</groupId>
73+
<artifactId>quarkus-messaging-kafka</artifactId>
74+
</dependency>
7175
<dependency>
7276
<groupId>io.quarkus</groupId>
7377
<artifactId>quarkus-junit5</artifactId>
7478
<scope>test</scope>
7579
</dependency>
80+
7681
<dependency>
7782
<groupId>io.rest-assured</groupId>
7883
<artifactId>rest-assured</artifactId>
7984
<scope>test</scope>
8085
</dependency>
86+
87+
<!-- Kafka Testing Dependencies -->
88+
<dependency>
89+
<groupId>io.quarkus</groupId>
90+
<artifactId>quarkus-junit5-mockito</artifactId>
91+
<scope>test</scope>
92+
</dependency>
93+
94+
<dependency>
95+
<groupId>org.awaitility</groupId>
96+
<artifactId>awaitility</artifactId>
97+
<scope>test</scope>
98+
</dependency>
99+
100+
<dependency>
101+
<groupId>org.mockito</groupId>
102+
<artifactId>mockito-core</artifactId>
103+
<scope>test</scope>
104+
</dependency>
81105
</dependencies>
82106

83107
<build>

sticker-award/src/main/java/com/datadoghq/stickerlandia/stickeraward/StickerAwardResource.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.datadoghq.stickerlandia.stickeraward.beans.UserStickersResponseApiResponse;
1111
import com.datadoghq.stickerlandia.stickeraward.entity.Sticker;
1212
import com.datadoghq.stickerlandia.stickeraward.entity.StickerAssignment;
13+
import com.datadoghq.stickerlandia.stickeraward.messaging.StickerEventPublisher;
1314
import io.smallrye.common.constraint.NotNull;
1415
import jakarta.inject.Inject;
1516
import jakarta.transaction.Transactional;
@@ -22,6 +23,8 @@
2223
import jakarta.ws.rs.Produces;
2324
import jakarta.ws.rs.core.Response;
2425
import org.eclipse.microprofile.openapi.annotations.Operation;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
2528

2629
import java.time.Instant;
2730
import java.util.ArrayList;
@@ -31,6 +34,11 @@
3134

3235
@Path("/api")
3336
public class StickerAwardResource {
37+
38+
private static final Logger log = LoggerFactory.getLogger(StickerAwardResource.class);
39+
40+
@Inject
41+
StickerEventPublisher eventPublisher;
3442

3543
@Operation(description = "Get stickers assigned to a user (access controlled based on caller identity)")
3644
@Path("/award/v1/users/{userId}/stickers")
@@ -100,6 +108,16 @@ public Response assignStickerToUser(@PathParam("userId") String userId,
100108
// Create new assignment
101109
StickerAssignment assignment = new StickerAssignment(userId, sticker, data.getReason());
102110
assignment.persist();
111+
112+
// Publish events to Kafka
113+
try {
114+
log.info("Publishing sticker assignment events for userId={}, stickerId={}", userId, stickerId);
115+
eventPublisher.publishStickerAssigned(assignment);
116+
} catch (Exception e) {
117+
log.error("Failed to publish sticker assignment events", e);
118+
// Continue with the response even if the event publishing fails
119+
// In a production system, you might want to implement a retry mechanism
120+
}
103121

104122
// Create response
105123
StickerAssignmentResponse response = new StickerAssignmentResponse();
@@ -139,6 +157,16 @@ public Response removeStickerFromUser(@PathParam("userId") String userId,
139157
// Mark as removed
140158
assignment.setRemovedAt(Instant.now());
141159
assignment.persist();
160+
161+
// Publish events to Kafka
162+
try {
163+
log.info("Publishing sticker removal event for userId={}, stickerId={}", userId, stickerId);
164+
eventPublisher.publishStickerRemoved(assignment);
165+
} catch (Exception e) {
166+
log.error("Failed to publish sticker removal event", e);
167+
// Continue with the response even if the event publishing fails
168+
// In a production system, you might want to implement a retry mechanism
169+
}
142170

143171
// Create response
144172
StickerRemovalResponse response = new StickerRemovalResponse();
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.datadoghq.stickerlandia.stickeraward.events;
2+
3+
import java.time.Instant;
4+
5+
/**
6+
* Event received when a certification is completed.
7+
* Subscribed from the 'certifications.certificationCompleted.v1' topic.
8+
*/
9+
public class CertificationCompletedEvent {
10+
11+
private String accountId;
12+
private String certificationId;
13+
private Instant completedAt;
14+
15+
public String getAccountId() {
16+
return accountId;
17+
}
18+
19+
public void setAccountId(String accountId) {
20+
this.accountId = accountId;
21+
}
22+
23+
public String getCertificationId() {
24+
return certificationId;
25+
}
26+
27+
public void setCertificationId(String certificationId) {
28+
this.certificationId = certificationId;
29+
}
30+
31+
public Instant getCompletedAt() {
32+
return completedAt;
33+
}
34+
35+
public void setCompletedAt(Instant completedAt) {
36+
this.completedAt = completedAt;
37+
}
38+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.datadoghq.stickerlandia.stickeraward.events;
2+
3+
/**
4+
* Base abstract class for all domain events.
5+
* This class should be extended by all event types.
6+
*/
7+
public abstract class DomainEvent {
8+
9+
private String eventName;
10+
private String eventVersion;
11+
12+
protected DomainEvent(String eventName, String eventVersion) {
13+
this.eventName = eventName;
14+
this.eventVersion = eventVersion;
15+
}
16+
17+
public String getEventName() {
18+
return eventName;
19+
}
20+
21+
public void setEventName(String eventName) {
22+
this.eventName = eventName;
23+
}
24+
25+
public String getEventVersion() {
26+
return eventVersion;
27+
}
28+
29+
public void setEventVersion(String eventVersion) {
30+
this.eventVersion = eventVersion;
31+
}
32+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.datadoghq.stickerlandia.stickeraward.events;
2+
3+
import java.time.Instant;
4+
5+
import com.datadoghq.stickerlandia.stickeraward.entity.StickerAssignment;
6+
7+
/**
8+
* Event published when a sticker is assigned to a user.
9+
* Published to the 'stickers.stickerAssignedToUser.v1' topic.
10+
*/
11+
public class StickerAssignedToUserEvent extends DomainEvent {
12+
13+
private static final String EVENT_NAME = "StickerAssignedToUser";
14+
private static final String EVENT_VERSION = "v1";
15+
16+
private String accountId;
17+
private String stickerId;
18+
private Instant assignedAt;
19+
20+
/**
21+
* Default constructor for serialization frameworks
22+
*/
23+
public StickerAssignedToUserEvent() {
24+
super(EVENT_NAME, EVENT_VERSION);
25+
}
26+
27+
/**
28+
* Create a new event from a sticker assignment entity
29+
*
30+
* @param assignment The sticker assignment entity
31+
* @return A new event instance
32+
*/
33+
public static StickerAssignedToUserEvent fromAssignment(StickerAssignment assignment) {
34+
StickerAssignedToUserEvent event = new StickerAssignedToUserEvent();
35+
event.setAccountId(assignment.getUserId());
36+
event.setStickerId(assignment.getSticker().getStickerId());
37+
event.setAssignedAt(assignment.getAssignedAt());
38+
return event;
39+
}
40+
41+
public String getAccountId() {
42+
return accountId;
43+
}
44+
45+
public void setAccountId(String accountId) {
46+
this.accountId = accountId;
47+
}
48+
49+
public String getStickerId() {
50+
return stickerId;
51+
}
52+
53+
public void setStickerId(String stickerId) {
54+
this.stickerId = stickerId;
55+
}
56+
57+
public Instant getAssignedAt() {
58+
return assignedAt;
59+
}
60+
61+
public void setAssignedAt(Instant assignedAt) {
62+
this.assignedAt = assignedAt;
63+
}
64+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.datadoghq.stickerlandia.stickeraward.events;
2+
3+
import com.datadoghq.stickerlandia.stickeraward.entity.StickerAssignment;
4+
5+
/**
6+
* Event published when a user claims a sticker.
7+
* Published to the 'users.stickerClaimed.v1' topic.
8+
*/
9+
public class StickerClaimedEvent extends DomainEvent {
10+
11+
private static final String EVENT_NAME = "StickerClaimed";
12+
private static final String EVENT_VERSION = "v1";
13+
14+
private String accountId;
15+
private String stickerId;
16+
17+
/**
18+
* Default constructor for serialization frameworks
19+
*/
20+
public StickerClaimedEvent() {
21+
super(EVENT_NAME, EVENT_VERSION);
22+
}
23+
24+
/**
25+
* Create a new event from a sticker assignment entity
26+
*
27+
* @param assignment The sticker assignment entity
28+
* @return A new event instance
29+
*/
30+
public static StickerClaimedEvent fromAssignment(StickerAssignment assignment) {
31+
StickerClaimedEvent event = new StickerClaimedEvent();
32+
event.setAccountId(assignment.getUserId());
33+
event.setStickerId(assignment.getSticker().getStickerId());
34+
return event;
35+
}
36+
37+
public String getAccountId() {
38+
return accountId;
39+
}
40+
41+
public void setAccountId(String accountId) {
42+
this.accountId = accountId;
43+
}
44+
45+
public String getStickerId() {
46+
return stickerId;
47+
}
48+
49+
public void setStickerId(String stickerId) {
50+
this.stickerId = stickerId;
51+
}
52+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.datadoghq.stickerlandia.stickeraward.events;
2+
3+
import java.time.Instant;
4+
5+
import com.datadoghq.stickerlandia.stickeraward.entity.StickerAssignment;
6+
7+
/**
8+
* Event published when a sticker is removed from a user.
9+
* Published to the 'stickers.stickerRemovedFromUser.v1' topic.
10+
*/
11+
public class StickerRemovedFromUserEvent extends DomainEvent {
12+
13+
private static final String EVENT_NAME = "StickerRemovedFromUser";
14+
private static final String EVENT_VERSION = "v1";
15+
16+
private String accountId;
17+
private String stickerId;
18+
private Instant removedAt;
19+
20+
/**
21+
* Default constructor for serialization frameworks
22+
*/
23+
public StickerRemovedFromUserEvent() {
24+
super(EVENT_NAME, EVENT_VERSION);
25+
}
26+
27+
/**
28+
* Create a new event from a sticker assignment entity
29+
*
30+
* @param assignment The sticker assignment entity with removed status
31+
* @return A new event instance
32+
*/
33+
public static StickerRemovedFromUserEvent fromAssignment(StickerAssignment assignment) {
34+
if (assignment.getRemovedAt() == null) {
35+
throw new IllegalArgumentException("Cannot create removal event from active assignment");
36+
}
37+
38+
StickerRemovedFromUserEvent event = new StickerRemovedFromUserEvent();
39+
event.setAccountId(assignment.getUserId());
40+
event.setStickerId(assignment.getSticker().getStickerId());
41+
event.setRemovedAt(assignment.getRemovedAt());
42+
return event;
43+
}
44+
45+
public String getAccountId() {
46+
return accountId;
47+
}
48+
49+
public void setAccountId(String accountId) {
50+
this.accountId = accountId;
51+
}
52+
53+
public String getStickerId() {
54+
return stickerId;
55+
}
56+
57+
public void setStickerId(String stickerId) {
58+
this.stickerId = stickerId;
59+
}
60+
61+
public Instant getRemovedAt() {
62+
return removedAt;
63+
}
64+
65+
public void setRemovedAt(Instant removedAt) {
66+
this.removedAt = removedAt;
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.datadoghq.stickerlandia.stickeraward.messaging;
2+
3+
import io.quarkus.kafka.client.serialization.ObjectMapperDeserializer;
4+
import com.datadoghq.stickerlandia.stickeraward.events.CertificationCompletedEvent;
5+
6+
/**
7+
* Deserializer for the CertificationCompletedEvent from Kafka.
8+
*/
9+
public class CertificationCompletedEventDeserializer extends ObjectMapperDeserializer<CertificationCompletedEvent> {
10+
11+
public CertificationCompletedEventDeserializer() {
12+
super(CertificationCompletedEvent.class);
13+
}
14+
}

0 commit comments

Comments
 (0)