Commit 5ceb875a authored by Alex Segers's avatar Alex Segers

Merge branch 'dev' into 'feature/manager_model'

# Conflicts:
#   backend/order-management/pom.xml
parents f4441680 e7b8d407
Pipeline #1664 failed with stage
File added
image: docker:19.03.12
services:
- name: docker:19.03.13-dind
variables:
#DOCKER_DRIVER: overlay
SPRING_PROFILES_ACTIVE: gitlab-ci
USER_GITLAB: vivaddadhi
APP_NAME: order-management
REPO: order-management-backend
DOCKER_HOST: tcp://docker:2375
before_script:
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN https://gitlab.mynisum.com/
stages:
# - build
- docker
# maven-build:
# image: maven:3-jdk-8
# stage: build
# script: "mvn -f ./backend/order-management/pom.xml clean package -B"
# artifacts:
# paths:
# - target/*jar
docker-build:
stage: docker
script:
- docker build -t registry.gitlab.com/vishalvaddadhi/order-management-backend -f ./backend/order-management/Dockerfile .
- docker push registry.gitlab.com/vishalvaddadhi/order-management-backend
# Default ignored files
/shelf/
/workspace.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
\ No newline at end of file
...@@ -14,6 +14,7 @@ target/ ...@@ -14,6 +14,7 @@ target/
.sts4-cache .sts4-cache
*.properties *.properties
*.yml
### IntelliJ IDEA ### ### IntelliJ IDEA ###
.idea .idea
......
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.1/apache-maven-3.8.1-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
FROM openjdk:8-alpine
VOLUME /tmp
ADD ./backend/order-management/target/orders.jar orders.jar
ENTRYPOINT ["java","-jar","/orders.jar"]
\ No newline at end of file
# Order-Management Backend
This is the backend for the order-management project.
## Installation
Clone this project and CD into the directory and open it up with your favorite editor or IDE of choice.
You are also going to want to install Apache Kafka and get the server running on your local machine before running the backend application.
To install the Apache Kafka , go to this [link](https://www.apache.org/dyn/closer.cgi?path=/kafka/2.8.0/kafka_2.13-2.8.0.tgz) and download the tar.
You then want to unzip the tar file and put it in whatever directory is easiest for you to access.
### Installation Via Homebrew
To install Kafka on your local machine via homebrew, run the following command:
```
brew install kafka
```
## Usage
You want to now open up two separate terminals for running the zookeeper and the kafka server. Once you have two terminals opened up CD into the apache kafka directory in both terminals and run these commands in order:
```
bin/zookeeper-server-start.sh config/zookeeper.properties
```
Now in the other terminal you want to run this command
```
bin/kafka-server-start.sh config/server.properties
```
### Usage Via Homebrew Installation
Once Kafka is installed, run the following command to start Kafka Zookeeper:
```
zookeeper-server-start /usr/local/etc/kafka/zookeeper.properties
```
Next, we need to start the Kafka server, use the following command to do so:
```
kafka-server-start /usr/local/etc/kafka/server.properties
```
Once you have both of these commands running you can now go into your IDE of choice or editor, and run the backend java application. Once the application is running you want to send a POST request - either via curl with a query param of "message=somethinghere" or using a service like postman and have a query params in there. The route you are going to want to hit is http://localhost:8080/kafka/publish.
Example request:
```
http://localhost:8080/kafka/publish?message=somethinghere
```
Once you hit the route mentioned above with the query param, go back to your IDE and you should see producer sending a message and consumer consuming that message.
Example Console Response:
```
2021-05-05 17:13:29.932 INFO 82715 --- [ntainer#0-0-C-1] c.afp.ordermanagement.service.Consumer : #### -> Consumed message -> somethinghere
```
...@@ -25,7 +25,26 @@ ...@@ -25,7 +25,26 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId> <artifactId>spring-boot-starter-webflux</artifactId>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>2.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.kafka/spring-kafka-dist -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-dist</artifactId>
<version>2.7.0</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
...@@ -62,6 +81,22 @@ ...@@ -62,6 +81,22 @@
<version>7.4.0</version> <version>7.4.0</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>1.5</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
...@@ -71,5 +106,4 @@ ...@@ -71,5 +106,4 @@
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</project> </project>
package com.afp.ordermanagement.config; package com.afp.ordermanagement.config;
//import com.google.common.base.Predicate;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
@Configuration @Configuration
public class AppConfig public class AppConfig {
{
@Bean @Bean
public static PropertyPlaceholderConfigurer getPropertyPlaceholderConfigurer() public static PropertyPlaceholderConfigurer getPropertyPlaceholderConfigurer()
{ {
...@@ -16,4 +17,6 @@ public class AppConfig ...@@ -16,4 +17,6 @@ public class AppConfig
ppc.setIgnoreUnresolvablePlaceholders(true); ppc.setIgnoreUnresolvablePlaceholders(true);
return ppc; return ppc;
} }
} }
package com.afp.ordermanagement.config;
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.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class KafkaConfig {
@Bean
public ProducerFactory<String, String> producerFactoryString() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public KafkaTemplate<String, String> kafkaTemplateString() {
return new KafkaTemplate<>(producerFactoryString());
}
@Bean
public ConsumerFactory<String, String> consumerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
configProps.put(ConsumerConfig.GROUP_ID_CONFIG, "group_id");
configProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
configProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
return new DefaultKafkaConsumerFactory<>(configProps);
}
@Bean
public ConcurrentKafkaListenerContainerFactory<String, String> kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
return factory;
}
}
package com.afp.ordermanagement.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Order Management PRO")
.build();
}
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.useDefaultResponseMessages(false)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
}
\ No newline at end of file
...@@ -2,10 +2,22 @@ package com.afp.ordermanagement.config; ...@@ -2,10 +2,22 @@ package com.afp.ordermanagement.config;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.EnableWebFlux; import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.ResourceHandlerRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.config.WebFluxConfigurer;
@Configuration @Configuration
@EnableWebFlux @EnableWebFlux
public class WebFluxConfig implements WebFluxConfigurer public class WebFluxConfig implements WebFluxConfigurer
{ {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui.html**")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
} }
\ No newline at end of file
package com.afp.ordermanagement.controller;
import com.afp.ordermanagement.service.Producer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value = "/kafka")
public class KafkaController {
private final Producer producer;
public KafkaController(Producer producer) {
this.producer = producer;
}
@PostMapping(value = "/publish/{id}")
public void sendMessageToKafkaTopic(@RequestParam String message) {
producer.sendMessage(message);
}
}
...@@ -2,6 +2,7 @@ package com.afp.ordermanagement.controller; ...@@ -2,6 +2,7 @@ package com.afp.ordermanagement.controller;
import com.afp.ordermanagement.model.Manager; import com.afp.ordermanagement.model.Manager;
import com.afp.ordermanagement.model.Order;
import com.afp.ordermanagement.repository.ManagerRepository; import com.afp.ordermanagement.repository.ManagerRepository;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
...@@ -24,4 +25,5 @@ public class ManagerController { ...@@ -24,4 +25,5 @@ public class ManagerController {
} }
} }
package com.afp.ordermanagement.controller; package com.afp.ordermanagement.controller;
import com.afp.ordermanagement.model.Order; import com.afp.ordermanagement.model.Order;
import com.afp.ordermanagement.repository.OrderRepository; import com.afp.ordermanagement.service.OrderService;
import com.afp.ordermanagement.service.Producer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RestController; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController @RestController
@RequestMapping("/api") @RequestMapping("/api")
public class OrderController { public class OrderController {
@Autowired @Autowired
OrderRepository orderRepository; private OrderService orderService;
@GetMapping("/order") @Autowired
public Flux<Order> getAllManagers() { Producer producer;
System.out.println("here");
Flux<Order> managerFlux = orderRepository.findAll();
return managerFlux; /**
* DESC - This route will let order manager get order status from warehouse
* @param orderId
*/
@GetMapping("/orderStatus/{orderId}")
public void getOrderStatusFromWarehouse(@PathVariable String orderId) {
producer.sendOrderId(orderId);
}
@GetMapping("/orders")
@CrossOrigin
public Flux<Order> getAllOrders(){
return orderService.getAllOrders();
}
@GetMapping("/orders/{orderId}")
@CrossOrigin
public Mono<Order> getOrderById(@PathVariable("orderId") String orderId) {
return orderService.getOrderById(orderId);
}
@GetMapping("/orders/byCustomer/{customerId}")
public Flux<Order> getAllOrdersByCustomerId(@PathVariable("customerId") String customerId) {
return orderService.getAllOrdersByCustomerId(customerId);
}
@PostMapping("/orders")
@ResponseStatus(HttpStatus.CREATED)
public Mono<Order> saveOrder(@RequestBody Order order){
return orderService.createOrder(order);
} }
@PutMapping("/order/{orderId}")
public Mono<ResponseEntity<Order>> updateOrder(@PathVariable(value = "orderId") String orderId, @RequestBody Order order){
return orderService.updateOrderByOrderId(orderId, order)
.map(updatedOrder -> ResponseEntity.ok(updatedOrder))
.defaultIfEmpty(ResponseEntity.notFound().build());
}
} }
package com.afp.ordermanagement.model;
import lombok.Data;
import java.util.Objects;
@Data
public class CustomerAddress {
private String street;
private String city;
private String state;
private String zip;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CustomerAddress)) return false;
CustomerAddress that = (CustomerAddress) o;
return getStreet().equals(that.getStreet()) && getCity().equals(that.getCity()) && getState().equals(that.getState()) && getZip().equals(that.getZip());
}
@Override
public int hashCode() {
return Objects.hash(getStreet(), getCity(), getState(), getZip());
}
@Override
public String toString() {
return "CustomerAddress{" +
"street='" + street + '\'' +
", city='" + city + '\'' +
", state='" + state + '\'' +
", zip='" + zip + '\'' +
'}';
}
}
package com.afp.ordermanagement.model;
import lombok.Data;
import java.util.Objects;
@Data
public class Item {
private String itemId;
private String itemName;
private String itemSku;
private int itemQuantity;
private double itemPrice;
}
package com.afp.ordermanagement.UNIT_TESTS.model; package com.afp.ordermanagement.UNIT_TESTS.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document; import org.springframework.data.mongodb.core.mapping.Document;
import java.util.List;
import java.util.Objects;
@Data
@AllArgsConstructor
@Document(collection = "orders") @Document(collection = "orders")
public class Order { public class Order {
@Id @Id
private String id; private String id;
private String emailAddress;
public String getId() { private String orderTrackingCode;
return id;
}
public void setId(String id) { private OrderStatus orderStatus;
this.id = id; private long orderCreatedAt;
} private long orderUpdatedAt;
public String getEmailAddress() { private String customerId;
return emailAddress; private String customerEmailAddress;
}
private List<Item> orderItems;
private CustomerAddress customerAddress;
public void setEmailAddress(String emailAddress) { public Order(){
this.emailAddress = emailAddress;
} }
} }
package com.afp.ordermanagement.model;
public enum OrderStatus {
RECEIVED,
FULFILLED,
CANCELLED
}
...@@ -3,7 +3,10 @@ package com.afp.ordermanagement.repository; ...@@ -3,7 +3,10 @@ package com.afp.ordermanagement.repository;
import com.afp.ordermanagement.model.Order; import com.afp.ordermanagement.model.Order;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository; import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
@Repository @Repository
public interface OrderRepository extends ReactiveMongoRepository<Order, String> { public interface OrderRepository extends ReactiveMongoRepository<Order, String> {
Flux<Order> findByCustomerId(String customerId);
} }
package com.afp.ordermanagement.service;
import com.afp.ordermanagement.model.Order;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;
@Service
public class Consumer {
private static final Logger logger = LoggerFactory.getLogger(Consumer.class);
@KafkaListener(topics = "managers", groupId = "group_id")
public void consumerManager(String message){
logger.info(String.format("#### -> Consumed message -> %s", message));
}
@KafkaListener(topics = "orders")
public void getOrderStatusFromWarehouse(String status) {
logger.info(String.format("#### -> Consumed order Status: %s", status));
}
}
package com.afp.ordermanagement.service; package com.afp.ordermanagement.service;
import com.afp.ordermanagement.model.Order;
import com.afp.ordermanagement.model.OrderStatus;
import com.afp.ordermanagement.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class OrderService { public class OrderService {
@Autowired
OrderRepository orderRepository;
public Mono<Order> createOrder(Order newOrder){
String defaultOrderTrackingCode = "N/A";
OrderStatus defaultOrderStatus = OrderStatus.RECEIVED;
long serviceSystemTime = System.currentTimeMillis();
newOrder.setOrderStatus(defaultOrderStatus);
newOrder.setOrderTrackingCode(defaultOrderTrackingCode);
newOrder.setOrderCreatedAt(serviceSystemTime);
newOrder.setOrderUpdatedAt(serviceSystemTime);
return orderRepository.save(newOrder);
}
public Flux<Order> getAllOrders(){
return orderRepository.findAll();
}
public Mono<Order> getOrderById(String orderId) {
return orderRepository.findById(orderId);
}
public Flux<Order> getAllOrdersByCustomerId(String customerId){
return orderRepository.findByCustomerId(customerId);
}
public Mono<Order> updateOrderByOrderId(String orderId, Order newOrder){
return orderRepository.findById(orderId)
.flatMap(existingOrder -> {
existingOrder.setCustomerAddress(newOrder.getCustomerAddress());
existingOrder.setCustomerEmailAddress(newOrder.getCustomerEmailAddress());
existingOrder.setOrderTrackingCode(newOrder.getOrderTrackingCode());
existingOrder.setOrderItems(newOrder.getOrderItems());
existingOrder.setOrderStatus(newOrder.getOrderStatus());
return orderRepository.save(existingOrder);
});
}
} }
package com.afp.ordermanagement.service;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.config.ConfigDataResourceNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.kafka.core.KafkaTemplate;
import java.sql.SQLOutput;
@Service
public class Producer {
private static final Logger logger = LoggerFactory.getLogger(Producer.class);
private static final String MANAGER_TOPIC = "managers";
private static final String ORDER_TOPIC = "orders";
private KafkaTemplate<String, String> kafkaTemplate;
public Producer(KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
/**
* DESC - Sending orderId as a message to ORDER_TOPIC for warehouse to consume
* @param id
* @throws ResourceNotFoundException
* @throws IllegalArgumentException
*/
public void sendOrderId(String id) {
try {
logger.info(String.format("#### -> Order id sent to warehouse: %s", id));
kafkaTemplate.send(ORDER_TOPIC, id);
} catch (ResourceNotFoundException e) {
logger.error("Order with that Id does not exist, exception caught: " + e);
} catch (IllegalArgumentException e) {
logger.error("Not a valid input, exception caught: " + e);
}
}
}
...@@ -3,10 +3,10 @@ package com.afp.ordermanagement; ...@@ -3,10 +3,10 @@ package com.afp.ordermanagement;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest //@SpringBootTest
class OrderManagementApplicationTests { class OrderManagementApplicationTests {
@Test //@Test
void contextLoads() { void contextLoads() {
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment