diff --git a/spring-boot-modules/spring-boot-libraries-3/drawio/eventuate-tram.drawio b/spring-boot-modules/spring-boot-libraries-3/drawio/eventuate-tram.drawio new file mode 100644 index 000000000000..b9daad2b6731 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/drawio/eventuate-tram.drawio @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-boot-modules/spring-boot-libraries-3/pom.xml b/spring-boot-modules/spring-boot-libraries-3/pom.xml index ebb3dbbf41b3..c1d470aa82e4 100644 --- a/spring-boot-modules/spring-boot-libraries-3/pom.xml +++ b/spring-boot-modules/spring-boot-libraries-3/pom.xml @@ -16,6 +16,11 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-web + + org.springframework.kafka spring-kafka @@ -27,6 +32,17 @@ ${postgresql.version} + + io.eventuate.tram.core + eventuate-tram-spring-jdbc-kafka + ${eventuate.tram.version} + + + io.eventuate.tram.core + eventuate-tram-spring-events + ${eventuate.tram.version} + + org.springframework.modulith spring-modulith-events-api @@ -84,6 +100,12 @@ test + + net.javacrumbs.json-unit + json-unit-assertj + ${json-unit-assertj.version} + test + org.awaitility awaitility @@ -112,7 +134,23 @@ 42.3.1 2.2.224 2.2.1 + 0.36.0.RELEASE + 3.5.0 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 21 + 21 + + + + + diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/Application.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/Application.java similarity index 77% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/Application.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/Application.java index aeaf57becd4d..4acf4847b716 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/Application.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/Application.java @@ -1,8 +1,11 @@ -package com.baeldung.springmodulith; +package com.baeldung; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +/** + * use the appropriate profile: eventuate|modulith + */ @SpringBootApplication public class Application { diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/CommentsController.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/CommentsController.java new file mode 100644 index 000000000000..5fb010fe30c4 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/CommentsController.java @@ -0,0 +1,32 @@ +package com.baeldung.eventuate.tram; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import com.baeldung.eventuate.tram.domain.CommentService; +import com.baeldung.eventuate.tram.domain.Comment; + +@RestController +public class CommentsController { + + private final CommentService commentService; + + public CommentsController(CommentService service) { + this.commentService = service; + } + + @PostMapping("/api/articles/{slug}/comments") + public ResponseEntity addComment(@RequestBody AddCommentDto dto, @PathVariable String slug) { + Comment comment = new Comment(dto.text(), slug, dto.commentAuthor()); + long id = commentService.save(comment); + return ResponseEntity.status(HttpStatus.CREATED) + .body(id); + } + + record AddCommentDto(String text, String commentAuthor) { + } +} diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/EventuateConfig.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/EventuateConfig.java new file mode 100644 index 000000000000..653d7b3c2841 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/EventuateConfig.java @@ -0,0 +1,13 @@ +package com.baeldung.eventuate.tram; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +import io.eventuate.tram.spring.events.publisher.TramEventsPublisherConfiguration; +import io.eventuate.tram.spring.messaging.producer.jdbc.TramMessageProducerJdbcConfiguration; + +@Configuration +@Import({ TramEventsPublisherConfiguration.class, TramMessageProducerJdbcConfiguration.class }) +class EventuateConfig { + +} diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/Comment.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/Comment.java new file mode 100644 index 000000000000..651f621ce417 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/Comment.java @@ -0,0 +1,42 @@ +package com.baeldung.eventuate.tram.domain; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; + +@Entity +public class Comment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String text; + private String articleSlug; + private String commentAuthor; + + public Comment(String text, String articleSlug, String commentAuthor) { + this.text = text; + this.articleSlug = articleSlug; + this.commentAuthor = commentAuthor; + } + + Comment() { + } + + public Long getId() { + return id; + } + + public String getText() { + return text; + } + + public String getArticleSlug() { + return articleSlug; + } + + public String getCommentAuthor() { + return commentAuthor; + } +} diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/CommentAddedEvent.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/CommentAddedEvent.java new file mode 100644 index 000000000000..dd30cfb9fba9 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/CommentAddedEvent.java @@ -0,0 +1,6 @@ +package com.baeldung.eventuate.tram.domain; + +import io.eventuate.tram.events.common.DomainEvent; + +record CommentAddedEvent(Long id, String articleSlug) implements DomainEvent { +} diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/CommentRepository.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/CommentRepository.java new file mode 100644 index 000000000000..b9fa666536ce --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/CommentRepository.java @@ -0,0 +1,6 @@ +package com.baeldung.eventuate.tram.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentRepository extends JpaRepository { +} diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/CommentService.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/CommentService.java new file mode 100644 index 000000000000..e4d2e291c5b5 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/eventuate/tram/domain/CommentService.java @@ -0,0 +1,35 @@ +package com.baeldung.eventuate.tram.domain; + +import static java.util.Collections.singletonList; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import io.eventuate.tram.events.publisher.DomainEventPublisher; +import jakarta.transaction.Transactional; + +@Service +public class CommentService { + + private static final Logger log = LoggerFactory.getLogger(CommentService.class); + + private final CommentRepository comments; + private final DomainEventPublisher domainEvents; + + public CommentService(CommentRepository commentRepository, DomainEventPublisher domainEvents) { + this.comments = commentRepository; + this.domainEvents = domainEvents; + } + + @Transactional + public Long save(Comment comment) { + Comment saved = this.comments.save(comment); + log.info("Comment created: {}", saved); + + CommentAddedEvent commentAdded = new CommentAddedEvent(saved.getId(), saved.getArticleSlug()); + domainEvents.publish("baeldung.comment.added", saved.getId(), singletonList(commentAdded)); + return saved.getId(); + } + +} diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/Order.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/Order.java similarity index 81% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/Order.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/Order.java index c448bd44ddde..1acc8b4d5f7e 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/Order.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/Order.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.application.events.orders; +package com.baeldung.spring.modulith.events.orders; import java.time.Instant; import java.util.List; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/OrderCompletedEvent.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/OrderCompletedEvent.java similarity index 65% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/OrderCompletedEvent.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/OrderCompletedEvent.java index 4344b336ac1b..1da9231ca22e 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/OrderCompletedEvent.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/OrderCompletedEvent.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.application.events.orders; +package com.baeldung.spring.modulith.events.orders; import java.time.Instant; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/OrderRepository.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/OrderRepository.java similarity index 91% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/OrderRepository.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/OrderRepository.java index 7c159e358206..e485c004019b 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/OrderRepository.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/OrderRepository.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.application.events.orders; +package com.baeldung.spring.modulith.events.orders; import java.util.ArrayList; import java.util.List; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/OrderService.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/OrderService.java similarity index 93% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/OrderService.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/OrderService.java index c60792813c1a..536d0623d7d4 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/orders/OrderService.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/orders/OrderService.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.application.events.orders; +package com.baeldung.spring.modulith.events.orders; import java.util.Arrays; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/rewards/LoyalCustomersRepository.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/rewards/LoyalCustomersRepository.java similarity index 94% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/rewards/LoyalCustomersRepository.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/rewards/LoyalCustomersRepository.java index 29ba6fa8e271..24e1c888fe48 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/rewards/LoyalCustomersRepository.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/rewards/LoyalCustomersRepository.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.application.events.rewards; +package com.baeldung.spring.modulith.events.rewards; import java.util.ArrayList; import java.util.List; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/rewards/LoyaltyPointsService.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/rewards/LoyaltyPointsService.java similarity index 81% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/rewards/LoyaltyPointsService.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/rewards/LoyaltyPointsService.java index 8cd1afe329bc..2f4cd084c72d 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/application/events/rewards/LoyaltyPointsService.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/events/rewards/LoyaltyPointsService.java @@ -1,9 +1,9 @@ -package com.baeldung.springmodulith.application.events.rewards; +package com.baeldung.spring.modulith.events.rewards; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; -import com.baeldung.springmodulith.application.events.orders.OrderCompletedEvent; +import com.baeldung.spring.modulith.events.orders.OrderCompletedEvent; @Service public class LoyaltyPointsService { diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/Article.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/Article.java similarity index 93% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/Article.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/Article.java index d52ed5afe5a8..fc03becdd11e 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/Article.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/Article.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.events.externalization; +package com.baeldung.spring.modulith.externalization; import static jakarta.persistence.GenerationType.*; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/ArticlePublishedEvent.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/ArticlePublishedEvent.java similarity index 75% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/ArticlePublishedEvent.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/ArticlePublishedEvent.java index e12b6dafe5ff..d189cc30fb85 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/ArticlePublishedEvent.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/ArticlePublishedEvent.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.events.externalization; +package com.baeldung.spring.modulith.externalization; import org.springframework.modulith.events.Externalized; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/ArticleRepository.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/ArticleRepository.java similarity index 76% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/ArticleRepository.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/ArticleRepository.java index f6351b6262b5..4a3ef1b699b9 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/ArticleRepository.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/ArticleRepository.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.events.externalization; +package com.baeldung.spring.modulith.externalization; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/Baeldung.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/Baeldung.java similarity index 94% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/Baeldung.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/Baeldung.java index 4b861a49c853..6aefaeee957b 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/Baeldung.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/Baeldung.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.events.externalization; +package com.baeldung.spring.modulith.externalization; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/EventExternalizationConfig.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/EventExternalizationConfig.java similarity index 97% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/EventExternalizationConfig.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/EventExternalizationConfig.java index 6555694df983..561b86dfa3f2 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/EventExternalizationConfig.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/EventExternalizationConfig.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.events.externalization; +package com.baeldung.spring.modulith.externalization; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.context.annotation.Bean; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/WeeklySummaryPublishedEvent.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/WeeklySummaryPublishedEvent.java similarity index 70% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/WeeklySummaryPublishedEvent.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/WeeklySummaryPublishedEvent.java index 2ae8713099c8..d3e2d01182e1 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/WeeklySummaryPublishedEvent.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/WeeklySummaryPublishedEvent.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.events.externalization; +package com.baeldung.spring.modulith.externalization; import org.springframework.modulith.events.Externalized; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/infra/ArticlePublishedKafkaProducer.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/infra/ArticlePublishedKafkaProducer.java similarity index 89% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/infra/ArticlePublishedKafkaProducer.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/infra/ArticlePublishedKafkaProducer.java index 17a88a73f706..9dc11aa75bf8 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/infra/ArticlePublishedKafkaProducer.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/infra/ArticlePublishedKafkaProducer.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.events.externalization.infra; +package com.baeldung.spring.modulith.externalization.infra; import org.springframework.context.event.EventListener; import org.springframework.kafka.core.KafkaOperations; @@ -6,7 +6,7 @@ import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.util.Assert; -import com.baeldung.springmodulith.events.externalization.ArticlePublishedEvent; +import com.baeldung.spring.modulith.externalization.ArticlePublishedEvent; //@Component // this is used in sections 3 and 4 of tha article diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/infra/PublicationEvents.java b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/infra/PublicationEvents.java similarity index 89% rename from spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/infra/PublicationEvents.java rename to spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/infra/PublicationEvents.java index bf0b96e78bd5..936b4d0e11a2 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/springmodulith/events/externalization/infra/PublicationEvents.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/java/com/baeldung/spring/modulith/externalization/infra/PublicationEvents.java @@ -1,6 +1,5 @@ -package com.baeldung.springmodulith.events.externalization.infra; +package com.baeldung.spring.modulith.externalization.infra; -import com.baeldung.springmodulith.events.externalization.ArticlePublishedEvent; import org.springframework.modulith.events.CompletedEventPublications; import org.springframework.modulith.events.IncompleteEventPublications; import org.springframework.stereotype.Component; @@ -8,6 +7,8 @@ import java.time.Duration; import java.time.Instant; +import com.baeldung.spring.modulith.externalization.ArticlePublishedEvent; + @Component class PublicationEvents { private final IncompleteEventPublications incompleteEvent; diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/resources/application-eventuate.yml b/spring-boot-modules/spring-boot-libraries-3/src/main/resources/application-eventuate.yml new file mode 100644 index 000000000000..c4968a68e748 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/main/resources/application-eventuate.yml @@ -0,0 +1,23 @@ +spring: + datasource: + url: jdbc:postgresql://localhost:5432/mydb + username: sa + password: password + driver-class-name: org.postgresql.Driver + jpa: + generate-ddl: true + properties: + hibernate: + hbm2ddl.auto: create + kafka: + bootstrap-servers: localhost:9092 + +logging: + level: + root: INFO + io.eventuate: DEBUG + +# not needed for this article: +spring.modulith: + republish-outstanding-events-on-restart: false + events.jdbc.schema-initialization.enabled: false \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-libraries-3/src/main/resources/application.yml b/spring-boot-modules/spring-boot-libraries-3/src/main/resources/application-modulith.yml similarity index 100% rename from spring-boot-modules/spring-boot-libraries-3/src/main/resources/application.yml rename to spring-boot-modules/spring-boot-libraries-3/src/main/resources/application-modulith.yml diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/eventuate/tram/EventuateTramLiveTest.java b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/eventuate/tram/EventuateTramLiveTest.java new file mode 100644 index 000000000000..e9e3ec8587ad --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/eventuate/tram/EventuateTramLiveTest.java @@ -0,0 +1,82 @@ +package com.baeldung.eventuate.tram; + +import static java.time.Duration.ofSeconds; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.testcontainers.shaded.org.awaitility.Awaitility.await; + +import java.io.File; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.kafka.KafkaProperties; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.testcontainers.containers.ComposeContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import com.baeldung.Application; +import com.baeldung.listener.TestKafkaListenerConfig; +import com.baeldung.listener.TestListener; + +@Testcontainers +@AutoConfigureMockMvc +@SpringBootTest(classes = { Application.class, TestKafkaListenerConfig.class }) +@ActiveProfiles({ "eventuate", "test-listeners"}) +class EventuateTramLiveTest { + + @Container + static ComposeContainer environment = new ComposeContainer( + new File("src/test/resources/eventuate-docker-compose.yml")) + .withLocalCompose(true); + + @Autowired + private MockMvc mockMvc; + + @Autowired + private TestListener testListener; + + @BeforeEach + void setUp() { + testListener.reset(); + } + + @Test + void whenSavingAnEntityToDB_thenPublishKafkaEvent() throws Exception { + String commentId = mockMvc.perform(post("/api/articles/oop-best-practices/comments") + .contentType(MediaType.APPLICATION_JSON) + .content(""" + { + "articleAuthor": "Andrey the Author", + "commentAuthor": "Richard the Reader", + "text": "Great article!" + } + """)) + .andExpect(status().is(201)) + .andReturn() + .getResponse() + .getContentAsString(); + + await().atMost(ofSeconds(30)) + .until(() -> testListener.getCommentAddedEvents().size() == 1); + + String eventJson = testListener.getCommentAddedEvents().getFirst(); + assertThatJson(eventJson) + .inPath("payload").asString() + .isEqualToIgnoringWhitespace(""" + { + "id": %s, + "articleSlug": "oop-best-practices" + } + """.formatted(commentId)); + } + +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/events/externalization/listener/TestKafkaListenerConfig.java b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/listener/TestKafkaListenerConfig.java similarity index 91% rename from spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/events/externalization/listener/TestKafkaListenerConfig.java rename to spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/listener/TestKafkaListenerConfig.java index c2ee9b24a20d..6960d931aab2 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/events/externalization/listener/TestKafkaListenerConfig.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/listener/TestKafkaListenerConfig.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.events.externalization.listener; +package com.baeldung.listener; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.context.annotation.Bean; @@ -28,4 +28,8 @@ ConsumerFactory consumerFactory(KafkaProperties kafkaProperties return new DefaultKafkaConsumerFactory<>(kafkaProperties.buildConsumerProperties()); } + @Bean + TestListener testListener() { + return new TestListener(); + } } \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/listener/TestListener.java b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/listener/TestListener.java new file mode 100644 index 000000000000..14c5063903b7 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/listener/TestListener.java @@ -0,0 +1,34 @@ +package com.baeldung.listener; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.kafka.annotation.KafkaListener; + +public class TestListener { + private List articlePublishedEvents = new ArrayList<>(); + private List commentAddedEvents = new ArrayList<>(); + + @KafkaListener(id = "test-id", topics = "baeldung.articles.published") + public void listen(String event) { + articlePublishedEvents.add(event); + } + + @KafkaListener(id = "test-id-2", topics = "baeldung.comment.added") + public void commentAddedEvents(String event) { + commentAddedEvents.add(event); + } + + public List getArticlePublishedEvents() { + return articlePublishedEvents; + } + + public List getCommentAddedEvents() { + return commentAddedEvents; + } + + public void reset() { + articlePublishedEvents = new ArrayList<>(); + commentAddedEvents = new ArrayList<>(); + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/EventListenerUnitTest.java b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/EventListenerUnitTest.java similarity index 76% rename from spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/EventListenerUnitTest.java rename to spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/EventListenerUnitTest.java index 676bc1173b39..795a126ddf2e 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/EventListenerUnitTest.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/EventListenerUnitTest.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.application.events; +package com.baeldung.spring.modulith.events; import static org.assertj.core.api.Assertions.assertThat; @@ -8,11 +8,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.test.context.ActiveProfiles; -import com.baeldung.springmodulith.application.events.orders.OrderCompletedEvent; -import com.baeldung.springmodulith.application.events.rewards.LoyalCustomersRepository; +import com.baeldung.spring.modulith.events.orders.OrderCompletedEvent; +import com.baeldung.spring.modulith.events.rewards.LoyalCustomersRepository; @SpringBootTest +@ActiveProfiles({ "modulith", "h2" }) class EventListenerUnitTest { @Autowired diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/EventPublisherUnitTest.java b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/EventPublisherUnitTest.java similarity index 82% rename from spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/EventPublisherUnitTest.java rename to spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/EventPublisherUnitTest.java index f4bdeee90dfa..a2b199bb0dc2 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/EventPublisherUnitTest.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/EventPublisherUnitTest.java @@ -1,15 +1,17 @@ -package com.baeldung.springmodulith.application.events; +package com.baeldung.spring.modulith.events; -import com.baeldung.springmodulith.application.events.orders.OrderService; +import com.baeldung.spring.modulith.events.orders.OrderService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.ComponentScan; +import org.springframework.test.context.ActiveProfiles; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest +@ActiveProfiles("h2") class EventPublisherUnitTest { @Autowired diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/SpringModulithScenarioApiUnitTest.java b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/SpringModulithScenarioApiUnitTest.java similarity index 74% rename from spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/SpringModulithScenarioApiUnitTest.java rename to spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/SpringModulithScenarioApiUnitTest.java index f36a0c30e628..a8393c6a2523 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/SpringModulithScenarioApiUnitTest.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/SpringModulithScenarioApiUnitTest.java @@ -1,21 +1,21 @@ -package com.baeldung.springmodulith.application.events; +package com.baeldung.spring.modulith.events; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Instant; -import com.baeldung.springmodulith.application.events.orders.OrderCompletedEvent; -import com.baeldung.springmodulith.application.events.orders.OrderService; -import com.baeldung.springmodulith.application.events.rewards.LoyalCustomersRepository; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.modulith.test.ApplicationModuleTest; -import org.springframework.modulith.test.ApplicationModuleTest.BootstrapMode; import org.springframework.modulith.test.Scenario; +import org.springframework.test.context.ActiveProfiles; -import java.time.Duration; -import java.time.Instant; - -import static java.time.Duration.ofMillis; -import static org.assertj.core.api.Assertions.assertThat; +import com.baeldung.spring.modulith.events.orders.OrderCompletedEvent; +import com.baeldung.spring.modulith.events.orders.OrderService; +import com.baeldung.spring.modulith.events.rewards.LoyalCustomersRepository; @ApplicationModuleTest +@ActiveProfiles({ "modulith", "h2" }) class SpringModulithScenarioApiUnitTest { @Autowired diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/TestEventListener.java b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/TestEventListener.java similarity index 78% rename from spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/TestEventListener.java rename to spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/TestEventListener.java index 8973a993551a..278f43a2a7b6 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/application/events/TestEventListener.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/events/TestEventListener.java @@ -1,4 +1,4 @@ -package com.baeldung.springmodulith.application.events; +package com.baeldung.spring.modulith.events; import java.util.ArrayList; import java.util.List; @@ -6,7 +6,7 @@ import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; -import com.baeldung.springmodulith.application.events.orders.OrderCompletedEvent; +import com.baeldung.spring.modulith.events.orders.OrderCompletedEvent; @Component class TestEventListener { diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/events/externalization/EventsExternalizationLiveTest.java b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/externalization/EventsExternalizationLiveTest.java similarity index 88% rename from spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/events/externalization/EventsExternalizationLiveTest.java rename to spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/externalization/EventsExternalizationLiveTest.java index a1b3dfe170ce..83770d796849 100644 --- a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/events/externalization/EventsExternalizationLiveTest.java +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/spring/modulith/externalization/EventsExternalizationLiveTest.java @@ -1,12 +1,14 @@ -package com.baeldung.springmodulith.events.externalization; +package com.baeldung.spring.modulith.externalization; + +import com.baeldung.Application; +import com.baeldung.listener.TestKafkaListenerConfig; +import com.baeldung.listener.TestListener; -import com.baeldung.springmodulith.Application; -import com.baeldung.springmodulith.events.externalization.listener.TestKafkaListenerConfig; -import com.baeldung.springmodulith.events.externalization.listener.TestListener; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; import org.testcontainers.containers.KafkaContainer; @@ -23,6 +25,7 @@ @Testcontainers @SpringBootTest(classes = { Application.class, TestKafkaListenerConfig.class }) +@ActiveProfiles({ "modulith", "test-listeners" }) class EventsExternalizationLiveTest { @Autowired @@ -65,7 +68,7 @@ void whenArticleIsSavedToDB_thenItIsAlsoPublishedToKafka() { baeldung.createArticle(article); await().untilAsserted(() -> - assertThat(listener.getEvents()) + assertThat(listener.getArticlePublishedEvents()) .hasSize(1) .first().asString() .contains("\"slug\":\"introduction-to-spring-boot\"") @@ -84,7 +87,7 @@ void whenPublishingMessageFails_thenArticleIsStillSavedToDB() { baeldung.createArticle(article); - assertThat(listener.getEvents()) + assertThat(listener.getArticlePublishedEvents()) .isEmpty(); assertThat(repository.findAll()) diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/events/externalization/listener/TestListener.java b/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/events/externalization/listener/TestListener.java deleted file mode 100644 index bf5a36f66fc9..000000000000 --- a/spring-boot-modules/spring-boot-libraries-3/src/test/java/com/baeldung/springmodulith/events/externalization/listener/TestListener.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.baeldung.springmodulith.events.externalization.listener; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.kafka.annotation.KafkaListener; -import org.springframework.stereotype.Component; - -@Component -public class TestListener { - private List events = new ArrayList<>(); - - @KafkaListener(id = "test-id", topics = "baeldung.articles.published") - public void listen(String event) { - events.add(event); - } - - public List getEvents() { - return events; - } - - public void reset() { - events = new ArrayList<>(); - } -} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/resources/application-h2.yml b/spring-boot-modules/spring-boot-libraries-3/src/test/resources/application-h2.yml new file mode 100644 index 000000000000..73df29d333d6 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/resources/application-h2.yml @@ -0,0 +1,6 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + username: sa + password: pass + driver-class-name: org.h2.Driver \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/resources/application-test-listeners.yml b/spring-boot-modules/spring-boot-libraries-3/src/test/resources/application-test-listeners.yml new file mode 100644 index 000000000000..3b79e795185d --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/resources/application-test-listeners.yml @@ -0,0 +1,7 @@ +spring.kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: test-group-id + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + auto-offset-reset: earliest \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/resources/eventuate-docker-compose.yml b/spring-boot-modules/spring-boot-libraries-3/src/test/resources/eventuate-docker-compose.yml new file mode 100644 index 000000000000..319b82bf74d4 --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/resources/eventuate-docker-compose.yml @@ -0,0 +1,56 @@ +version: '3.8' + +services: + zookeeper: + image: eventuateio/eventuate-zookeeper:0.20.0.RELEASE + ports: + - 2181:2181 + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + KAFKA_HEAP_OPTS: -Xmx64m + + kafka: + image: eventuateio/eventuate-kafka:0.20.0.RELEASE + ports: + - 9092:9092 + - 29092:29092 + depends_on: + - zookeeper + environment: + KAFKA_LISTENERS: LC://kafka:29092,LX://kafka:9092 + KAFKA_ADVERTISED_LISTENERS: LC://kafka:29092,LX://${DOCKER_HOST_IP:-localhost}:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LC:PLAINTEXT,LX:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: LC + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_HEAP_OPTS: -Xmx192m + + postgres: + image: eventuateio/eventuate-postgres:0.20.0.RELEASE + restart: always + environment: + POSTGRES_USER: sa + POSTGRES_PASSWORD: password + POSTGRES_DB: mydb + ports: + - "5432:5432" + + cdcservice: + image: eventuateio/eventuate-cdc-service:0.18.0.RELEASE + ports: + - "8099:8080" + depends_on: + - postgres + - kafka + - zookeeper + environment: + SPRING_PROFILES_ACTIVE: EventuatePolling + SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/mydb + SPRING_DATASOURCE_USERNAME: sa + SPRING_DATASOURCE_PASSWORD: password + SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver + EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 + EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 + EVENTUATELOCAL_CDC_READER_NAME: PostgresWalReader + EVENTUATE_OUTBOX_ID: 1 + diff --git a/spring-boot-modules/spring-boot-libraries-3/src/test/resources/post-comment.bat b/spring-boot-modules/spring-boot-libraries-3/src/test/resources/post-comment.bat new file mode 100644 index 000000000000..17399e3ab3bb --- /dev/null +++ b/spring-boot-modules/spring-boot-libraries-3/src/test/resources/post-comment.bat @@ -0,0 +1,3 @@ +curl --location "http://localhost:8080/api/articles/oop-best-practices/comments" ^ +--header "Content-Type: application/json" ^ +--data "{\"articleAuthor\": \"Andrey the Author\", \"text\": \"Great article!\", \"commentAuthor\": \"Richard the Reader\"}" \ No newline at end of file