From f47bce7e7d895ee428e2824182c53ebb489925ca Mon Sep 17 00:00:00 2001 From: mahdiJ2001 Date: Sat, 23 Nov 2024 17:56:51 +0100 Subject: [PATCH] Added kafkaConsumer for inventory + InventoryEvent(dto) --- .idea/.gitignore | 3 + .idea/compiler.xml | 20 ++++ .idea/encodings.xml | 9 ++ .idea/jarRepositories.xml | 20 ++++ .idea/misc.xml | 15 +++ .idea/modules.xml | 8 ++ ...projet-soa-ecommerce-enit-2024-3AINFO2.iml | 9 ++ .idea/vcs.xml | 6 + catalog/catalog-service/pom.xml | 2 + .../java/org/ecommerce/domain/Product.java | 66 +++++------ .../org/ecommerce/dto/InventoryEvent.java | 60 ++++++++++ .../repository/ProductCategoryRepository.java | 29 ++++- .../service/KafkaProductConsumer.java | 112 ++++++++++++++++++ .../service/ProductCategoryService.java | 27 ++++- .../src/main/resources/application.properties | 30 +++-- 15 files changed, 363 insertions(+), 53 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/encodings.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/projet-soa-ecommerce-enit-2024-3AINFO2.iml create mode 100644 .idea/vcs.xml create mode 100644 catalog/catalog-service/src/main/java/org/ecommerce/dto/InventoryEvent.java create mode 100644 catalog/catalog-service/src/main/java/org/ecommerce/service/KafkaProductConsumer.java diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..7cf36b88 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 00000000..41145ea2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..712ab9d9 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..ac5aeea4 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..3428257d --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/projet-soa-ecommerce-enit-2024-3AINFO2.iml b/.idea/projet-soa-ecommerce-enit-2024-3AINFO2.iml new file mode 100644 index 00000000..d6ebd480 --- /dev/null +++ b/.idea/projet-soa-ecommerce-enit-2024-3AINFO2.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/catalog/catalog-service/pom.xml b/catalog/catalog-service/pom.xml index fe5bab14..0dab52e1 100644 --- a/catalog/catalog-service/pom.xml +++ b/catalog/catalog-service/pom.xml @@ -90,6 +90,8 @@ io.quarkus quarkus-jdbc-postgresql + + diff --git a/catalog/catalog-service/src/main/java/org/ecommerce/domain/Product.java b/catalog/catalog-service/src/main/java/org/ecommerce/domain/Product.java index 7547aba6..214b132e 100644 --- a/catalog/catalog-service/src/main/java/org/ecommerce/domain/Product.java +++ b/catalog/catalog-service/src/main/java/org/ecommerce/domain/Product.java @@ -19,29 +19,28 @@ public class Product { @Column private LocalDateTime createdAt = LocalDateTime.now(); @ManyToOne(optional = false) - @JoinColumn(name = "category_id",nullable = false) + @JoinColumn(name = "category_id", nullable = false) private ProductCategory category; private double basePrice; - private double shownPrice=basePrice; + private double shownPrice = basePrice; + private boolean disponibility; + public Product() { } - - public Product(String productName, String description, LocalDateTime createdAt, ProductCategory category, - double basePrice, double shownPrice) { + double basePrice, double shownPrice, boolean disponibility) { this.productName = productName; this.description = description; this.createdAt = createdAt; this.category = category; this.basePrice = basePrice; this.shownPrice = shownPrice; + this.disponibility = disponibility; } - - public Product(UUID id, String productName, String description, LocalDateTime createdAt, ProductCategory category, - double basePrice, double shownPrice) { + double basePrice, double shownPrice, boolean disponibility) { this.id = id; this.productName = productName; this.description = description; @@ -49,72 +48,72 @@ public Product(UUID id, String productName, String description, LocalDateTime cr this.category = category; this.basePrice = basePrice; this.shownPrice = shownPrice; + this.disponibility = disponibility; } - - public UUID getId() { return id; } + public void setId(UUID id) { this.id = id; } + public String getProductName() { return productName; } + public void setProductName(String productName) { this.productName = productName; } + public String getDescription() { return description; } + public void setDescription(String description) { this.description = description; } + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } - - public ProductCategory getCategory() { return category; } - - - public double getBasePrice() { - return basePrice; - } - - - - public double getShownPrice() { - return shownPrice; - } - - - public void setCategory(ProductCategory category) { this.category = category; } - + public double getBasePrice() { + return basePrice; + } public void setBasePrice(double basePrice) { this.basePrice = basePrice; } - + public double getShownPrice() { + return shownPrice; + } public void setShownPrice(double shownPrice) { this.shownPrice = shownPrice; } + public boolean isDisponibility() { + return disponibility; + } + public void setDisponibility(boolean disponibility) { + this.disponibility = disponibility; + } @Override public int hashCode() { @@ -130,11 +129,10 @@ public int hashCode() { result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(shownPrice); result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + (disponibility ? 1231 : 1237); return result; } - - @Override public boolean equals(Object obj) { if (this == obj) @@ -173,17 +171,15 @@ public boolean equals(Object obj) { return false; if (Double.doubleToLongBits(shownPrice) != Double.doubleToLongBits(other.shownPrice)) return false; + if (disponibility != other.disponibility) + return false; return true; } - - @Override public String toString() { return "Product [id=" + id + ", productName=" + productName + ", description=" + description + ", createdAt=" + createdAt + ", category=" + category + ", basePrice=" + basePrice + ", shownPrice=" + shownPrice - + "]"; + + ", disponibility=" + disponibility + "]"; } - - } diff --git a/catalog/catalog-service/src/main/java/org/ecommerce/dto/InventoryEvent.java b/catalog/catalog-service/src/main/java/org/ecommerce/dto/InventoryEvent.java new file mode 100644 index 00000000..1464e1b3 --- /dev/null +++ b/catalog/catalog-service/src/main/java/org/ecommerce/dto/InventoryEvent.java @@ -0,0 +1,60 @@ +package org.ecommerce.dto; + +public class InventoryEvent { + + private String eventType; + private String productId; + private String description; + private String category; + private double basePrice; + private boolean disponibility; + + + public String getEventType() { + return eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + + public String getProductId() { + return productId; + } + + public void setProductId(String productId) { + this.productId = productId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public double getBasePrice() { + return basePrice; + } + + public void setBasePrice(double basePrice) { + this.basePrice = basePrice; + } + + public boolean isDisponibility() { + return disponibility; + } + + public void setDisponibility(boolean disponibility) { + this.disponibility = disponibility; + } +} diff --git a/catalog/catalog-service/src/main/java/org/ecommerce/repository/ProductCategoryRepository.java b/catalog/catalog-service/src/main/java/org/ecommerce/repository/ProductCategoryRepository.java index 1e5403f8..c31ed80f 100644 --- a/catalog/catalog-service/src/main/java/org/ecommerce/repository/ProductCategoryRepository.java +++ b/catalog/catalog-service/src/main/java/org/ecommerce/repository/ProductCategoryRepository.java @@ -4,12 +4,11 @@ import jakarta.inject.Inject; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; - -import java.util.UUID; - import org.ecommerce.domain.ProductCategory; import org.ecommerce.exceptions.EntityNotFoundException; +import java.util.UUID; + @ApplicationScoped public class ProductCategoryRepository { @@ -19,17 +18,35 @@ public class ProductCategoryRepository { public ProductCategory findByName(String categoryName) throws EntityNotFoundException { try { return em.createQuery("SELECT c FROM ProductCategory c WHERE c.categoryName = :categoryName", ProductCategory.class) - .setParameter("categoryName", categoryName) - .getSingleResult(); + .setParameter("categoryName", categoryName) + .getSingleResult(); } catch (Exception e) { throw new EntityNotFoundException("Cannot find category with name: " + categoryName); } } + public ProductCategory findById(UUID id) throws EntityNotFoundException { + ProductCategory category = em.find(ProductCategory.class, id); + if (category == null) { + throw new EntityNotFoundException("Category not found for ID: " + id); + } + return category; + } + @Transactional public ProductCategory insert(ProductCategory category) { - category.setId(UUID.randomUUID()); + category.setId(UUID.randomUUID()); em.persist(category); return category; } + + @Transactional + public ProductCategory update(ProductCategory category) { + return em.merge(category); + } + + @Transactional + public void delete(ProductCategory category) { + em.remove(em.contains(category) ? category : em.merge(category)); + } } diff --git a/catalog/catalog-service/src/main/java/org/ecommerce/service/KafkaProductConsumer.java b/catalog/catalog-service/src/main/java/org/ecommerce/service/KafkaProductConsumer.java new file mode 100644 index 00000000..08017f4f --- /dev/null +++ b/catalog/catalog-service/src/main/java/org/ecommerce/service/KafkaProductConsumer.java @@ -0,0 +1,112 @@ +package org.ecommerce.service; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import org.eclipse.microprofile.reactive.messaging.Incoming; +import org.ecommerce.domain.Product; +import org.ecommerce.domain.ProductCategory; +import org.ecommerce.dto.InventoryEvent; +import org.ecommerce.exceptions.EntityNotFoundException; +import org.ecommerce.repository.ProductCategoryRepository; +import org.ecommerce.repository.ProductRepository; + +import java.util.UUID; + +@ApplicationScoped +public class KafkaProductConsumer { + + @Inject + ProductService productService; + + @Inject + ProductCategoryRepository categoryRepo; + + @Inject + ProductRepository productRepo; + + @Transactional + @Incoming("products-created") + public void handleInventoryEvent(String payload) { + try { + var objectMapper = new com.fasterxml.jackson.databind.ObjectMapper(); + InventoryEvent event = objectMapper.readValue(payload, InventoryEvent.class); + + switch (event.getEventType()) { + case "CREATE": + handleCreateEvent(event); + break; + case "UPDATE": + handleUpdateEvent(event); + break; + case "DELETE": + handleDeleteEvent(event); + break; + default: + System.err.println("Unknown event type: " + event.getEventType()); + } + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Failed to process Kafka message: " + payload); + } + } + + private void handleCreateEvent(InventoryEvent event) { + try { + ProductCategory category = getOrCreateCategory(event.getCategory()); + Product product = new Product( + UUID.fromString(event.getProductId()), + event.getDescription(), + event.getDescription(), + java.time.LocalDateTime.now(), + category, + event.getBasePrice(), + event.getBasePrice(), + event.isDisponibility() + ); + productService.add(product, event.getCategory()); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Failed to handle create event for product: " + event.getProductId()); + } + } + + private void handleUpdateEvent(InventoryEvent event) { + try { + Product product = productRepo.findById(UUID.fromString(event.getProductId())); + if (product != null) { + product.setDescription(event.getDescription()); + product.setBasePrice(event.getBasePrice()); + product.setDisponibility(event.isDisponibility()); + ProductCategory category = getOrCreateCategory(event.getCategory()); + product.setCategory(category); + productRepo.update(product); + } else { + System.err.println("Product not found for update: " + event.getProductId()); + } + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Failed to handle update event for product: " + event.getProductId()); + } + } + + private void handleDeleteEvent(InventoryEvent event) { + try { + productRepo.delete(UUID.fromString(event.getProductId())); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Failed to handle delete event for product: " + event.getProductId()); + } + } + + private ProductCategory getOrCreateCategory(String categoryName) { + ProductCategory category = null; + try { + category = categoryRepo.findByName(categoryName); + } catch (EntityNotFoundException e) { + category = new ProductCategory(UUID.randomUUID(), categoryName); + categoryRepo.insert(category); + } + return category; + } +} diff --git a/catalog/catalog-service/src/main/java/org/ecommerce/service/ProductCategoryService.java b/catalog/catalog-service/src/main/java/org/ecommerce/service/ProductCategoryService.java index 05e80513..3a059286 100644 --- a/catalog/catalog-service/src/main/java/org/ecommerce/service/ProductCategoryService.java +++ b/catalog/catalog-service/src/main/java/org/ecommerce/service/ProductCategoryService.java @@ -5,8 +5,11 @@ import jakarta.transaction.Transactional; import org.ecommerce.domain.ProductCategory; import org.ecommerce.exceptions.EntityAlreadyExistsException; +import org.ecommerce.exceptions.EntityNotFoundException; import org.ecommerce.repository.ProductCategoryRepository; +import java.util.UUID; + @ApplicationScoped public class ProductCategoryService { @@ -15,6 +18,28 @@ public class ProductCategoryService { @Transactional public ProductCategory addCategory(ProductCategory category) throws EntityAlreadyExistsException { - return categoryRepo.insert(category); + return categoryRepo.insert(category); + } + + @Transactional + public ProductCategory updateCategory(ProductCategory category) throws EntityNotFoundException { + ProductCategory existingCategory = categoryRepo.findByName(category.getCategoryName()); + if (existingCategory == null) { + throw new EntityNotFoundException("Category with name " + category.getCategoryName() + " not found"); + } + return categoryRepo.update(category); + } + + @Transactional + public void removeCategory(UUID id) throws EntityNotFoundException { + ProductCategory category = categoryRepo.findById(id); + if (category == null) { + throw new EntityNotFoundException("Category not found for ID: " + id); + } + categoryRepo.delete(category); + } + + public ProductCategory getCategoryByName(String categoryName) throws EntityNotFoundException { + return categoryRepo.findByName(categoryName); } } diff --git a/catalog/catalog-service/src/main/resources/application.properties b/catalog/catalog-service/src/main/resources/application.properties index 3df37f4b..2b1d8816 100644 --- a/catalog/catalog-service/src/main/resources/application.properties +++ b/catalog/catalog-service/src/main/resources/application.properties @@ -1,27 +1,35 @@ +# HTTP and Database Configurations quarkus.http.port=8082 -quarkus.datasource.db-kind = postgresql +quarkus.datasource.db-kind=postgresql quarkus.datasource.username=postgres quarkus.datasource.password=azerty -quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5434/db_catalog -#updated from linux -# drop and create the database at startup (use `update` to only update the schema) +quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5434/db_catalog +# Drop and create the database at startup (use `update` to only update the schema) quarkus.hibernate-orm.database.generation=update + %dev.quarkus.hibernate-orm.log.sql=true %dev.quarkus.hibernate-orm.log.format-sql=true %dev.quarkus.hibernate-orm.statistics=true + +# HTTP limits quarkus.http.limits.max-form-attribute-size=4M -#rest client apis + +# REST Client APIs quarkus.rest-client.pricing-api.url=http://localhost:8086 quarkus.rest-client.pricing-api.scope=javax.inject.Singleton quarkus.http.access-log.enabled=true -#kafka configuration -%prod.kafka.bootstrap.servers=kafka:9092 +# Kafka Configuration for Producer (Used in production) +%prod.kafka.bootstrap.servers=kafka:9092 + +# Kafka configuration for outgoing messages (Producer) mp.messaging.outgoing.products-out.connector=smallrye-kafka mp.messaging.outgoing.products-out.topic=products-created mp.messaging.outgoing.products-out.topic=products-updated -#consumer for testing -#mp.messaging.incoming.products-in.connector=smallrye-kafka -#mp.messaging.incoming.products-in.topic=products-created -#mp.messaging.incoming.products-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer + +# Kafka configuration for incoming messages (Consumer for testing) +mp.messaging.incoming.products-in.connector=smallrye-kafka +mp.messaging.incoming.products-in.topic=products-created +mp.messaging.incoming.products-in.value.deserializer=org.apache.kafka.common.serialization.StringDeserializer +mp.messaging.incoming.products-in.group.id=products-consumer-group