diff --git a/.gitlab/merge_request_templates/mr.md b/.gitlab/merge_request_templates/merge_request_template.md similarity index 100% rename from .gitlab/merge_request_templates/mr.md rename to .gitlab/merge_request_templates/merge_request_template.md diff --git a/.mvn/parent.xml b/.mvn/parent.xml index 73712c1..e859bb9 100644 --- a/.mvn/parent.xml +++ b/.mvn/parent.xml @@ -310,9 +310,7 @@ maven-checkstyle-plugin 3.5.0 - UTF-8 true - false diff --git a/README.md b/README.md index 60fdb45..e5fc028 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Example application to create appointments (REST API). Appointments are stored i * No input ports: they don't need to be decoupled, they just use the domain (and that's acceptable). ## 📖 Architecture -![Architecture Diagram](doc/architecture.svg) +![Architecture Diagram](https://raw.githubusercontent.com/jaguililla/hexagonal_spring/main/doc/architecture.svg) * **Port**: interface to set a boundary between application logic and implementation details. * **Adapter**: port implementation to connect the application domain with the system's context. * **Domain**: application logic and model entities. diff --git a/src/main/java/com/github/jaguililla/appointments/ApplicationConfiguration.java b/src/main/java/com/github/jaguililla/appointments/ApplicationConfiguration.java index 3d9735a..6bc74c1 100644 --- a/src/main/java/com/github/jaguililla/appointments/ApplicationConfiguration.java +++ b/src/main/java/com/github/jaguililla/appointments/ApplicationConfiguration.java @@ -5,30 +5,18 @@ import com.github.jaguililla.appointments.domain.AppointmentsService; import com.github.jaguililla.appointments.domain.UsersRepository; import com.github.jaguililla.appointments.output.notifiers.KafkaTemplateAppointmentsNotifier; -import com.github.jaguililla.appointments.output.repositories.JdbcTemplateAppointmentsRepository; -import com.github.jaguililla.appointments.output.repositories.JdbcTemplateUsersRepository; -import org.apache.kafka.clients.admin.AdminClientConfig; -import org.apache.kafka.clients.admin.NewTopic; -import org.apache.kafka.clients.consumer.ConsumerConfig; -import org.apache.kafka.clients.producer.ProducerConfig; -import org.apache.kafka.common.serialization.StringDeserializer; -import org.apache.kafka.common.serialization.StringSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.kafka.core.*; -import javax.sql.DataSource; -import java.util.Map; @Configuration class ApplicationConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationConfiguration.class); - @Value(value = "${spring.kafka.bootstrap-servers}") - private String bootstrapAddress; @Value(value = "${notifierTopic}") private String notifierTopic; @Value(value = "${createMessage}") @@ -36,34 +24,34 @@ class ApplicationConfiguration { @Value(value = "${deleteMessage}") private String deleteMessage; - @Bean - public KafkaAdmin kafkaAdmin() { - return new KafkaAdmin(Map.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress)); - } +// @Bean +// public KafkaAdmin kafkaAdmin() { +// return new KafkaAdmin(Map.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress)); +// } - @Bean - public NewTopic appointmentsTopic() { - return new NewTopic("appointments", 1, (short) 1); - } +// @Bean +// public NewTopic appointmentsTopic() { +// return new NewTopic("appointments", 1, (short) 1); +// } - @Bean - public ProducerFactory producerFactory() { - return new DefaultKafkaProducerFactory<>(Map.of( - ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress, - ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class, - ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class - )); - } +// @Bean +// public ProducerFactory producerFactory() { +// return new DefaultKafkaProducerFactory<>(Map.of( +// ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress, +// ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class, +// ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class +// )); +// } - @Bean - public ConsumerFactory consumerFactory() { - return new DefaultKafkaConsumerFactory<>(Map.of( - ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress, - ConsumerConfig.GROUP_ID_CONFIG, "group", - ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class, - ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class - )); - } +// @Bean +// public ConsumerFactory consumerFactory() { +// return new DefaultKafkaConsumerFactory<>(Map.of( +// ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapAddress, +// ConsumerConfig.GROUP_ID_CONFIG, "group", +// ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class, +// ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class +// )); +// } @Bean public KafkaTemplate kafkaTemplate( diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c0e6a93..df322f7 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -20,3 +20,10 @@ spring: kafka: bootstrap-servers: ${KAFKA_SERVER:localhost:9092} + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer + consumer: + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + group-id: group diff --git a/src/test/java/com/github/jaguililla/appointments/ApplicationIT.java b/src/test/java/com/github/jaguililla/appointments/ApplicationIT.java index 3d2f29f..e8a10f4 100644 --- a/src/test/java/com/github/jaguililla/appointments/ApplicationIT.java +++ b/src/test/java/com/github/jaguililla/appointments/ApplicationIT.java @@ -7,12 +7,15 @@ import com.github.jaguililla.appointments.http.controllers.messages.AppointmentRequest; import com.github.jaguililla.appointments.http.controllers.messages.AppointmentResponse; +import java.time.Duration; +import java.util.List; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.kafka.core.ConsumerFactory; import org.springframework.kafka.core.KafkaTemplate; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; @@ -34,6 +37,8 @@ class ApplicationIT { private final TestTemplate client; @Autowired private KafkaTemplate kafkaTemplate; + @Autowired + private ConsumerFactory consumerFactory; ApplicationIT(@LocalServerPort final int portTest) { client = new TestTemplate("http://localhost:" + portTest); @@ -83,6 +88,13 @@ void existing_appointments_can_be_fetched() { @Test void appointments_can_be_created_read_and_deleted() { + try (var consumer = consumerFactory.createConsumer()) { + consumer.subscribe(List.of("appointments")); + for (var r : consumer.poll(Duration.ZERO)) { + + } + } + client.post("/appointments", new AppointmentRequest() .id(UUID.randomUUID()) .startTimestamp(LocalDateTime.now())