Compare commits
2 Commits
master
...
saga-rollb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00513acf31 | ||
|
|
4c7c3d3345 |
@@ -37,7 +37,7 @@ stages:
|
||||
pool:
|
||||
vmImage: $(vmImageName)
|
||||
steps:
|
||||
- task: CacheBeta@1
|
||||
- task: Cache@2
|
||||
inputs:
|
||||
key: 'maven | "$(Agent.OS)" | **/pom.xml'
|
||||
restoreKeys: |
|
||||
@@ -48,11 +48,24 @@ stages:
|
||||
- task: Maven@3
|
||||
inputs:
|
||||
mavenPomFile: 'pom.xml'
|
||||
options: '-Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
|
||||
options: '-Dmaven.test.skip=true -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
|
||||
javaHomeOption: 'JDKVersion'
|
||||
jdkVersionOption: '1.8'
|
||||
jdkArchitectureOption: 'x64'
|
||||
goals: 'package'
|
||||
- task: CopyFiles@2
|
||||
inputs:
|
||||
SourceFolder: '$(system.defaultworkingdirectory)'
|
||||
Contents: |
|
||||
**/*.jar
|
||||
Dockerfile
|
||||
kubernetes/*
|
||||
TargetFolder: '$(build.artifactstagingdirectory)'
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
|
||||
ArtifactName: 'artifact'
|
||||
publishLocation: 'Container'
|
||||
- task: Docker@2
|
||||
inputs:
|
||||
containerRegistry: $(containerRegistryDockerConnection)
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
version: 0.2
|
||||
|
||||
env:
|
||||
variables:
|
||||
_PROJECT_NAME: "products"
|
||||
|
||||
phases:
|
||||
install:
|
||||
runtime-versions:
|
||||
java: openjdk8
|
||||
docker: 18
|
||||
commands:
|
||||
- echo install kubectl
|
||||
- curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
|
||||
- chmod +x ./kubectl
|
||||
- mv ./kubectl /usr/local/bin/kubectl
|
||||
pre_build:
|
||||
commands:
|
||||
- echo Logging in to Amazon ECR...
|
||||
- echo $_PROJECT_NAME
|
||||
- echo $AWS_ACCOUNT_ID
|
||||
- echo $AWS_DEFAULT_REGION
|
||||
- echo $CODEBUILD_RESOLVED_SOURCE_VERSION
|
||||
- echo start command
|
||||
- $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
|
||||
build:
|
||||
commands:
|
||||
- echo Build started on `date`
|
||||
- echo Building the Docker image...
|
||||
- mvn package -B
|
||||
- docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$_PROJECT_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION .
|
||||
post_build:
|
||||
commands:
|
||||
- echo Pushing the Docker image...
|
||||
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$_PROJECT_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION
|
||||
- echo connect kubectl
|
||||
- kubectl config set-cluster k8s --server="$KUBE_URL" --insecure-skip-tls-verify=true
|
||||
- kubectl config set-credentials admin --token="$KUBE_TOKEN"
|
||||
- kubectl config set-context default --cluster=k8s --user=admin
|
||||
- kubectl config use-context default
|
||||
- |
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: $_PROJECT_NAME
|
||||
labels:
|
||||
app: $_PROJECT_NAME
|
||||
spec:
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: $_PROJECT_NAME
|
||||
EOF
|
||||
- |
|
||||
cat <<EOF | kubectl apply -f -
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: $_PROJECT_NAME
|
||||
labels:
|
||||
app: $_PROJECT_NAME
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: $_PROJECT_NAME
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: $_PROJECT_NAME
|
||||
spec:
|
||||
containers:
|
||||
- name: $_PROJECT_NAME
|
||||
image: $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$_PROJECT_NAME:$CODEBUILD_RESOLVED_SOURCE_VERSION
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
timeoutSeconds: 2
|
||||
periodSeconds: 5
|
||||
failureThreshold: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health
|
||||
port: 8080
|
||||
initialDelaySeconds: 120
|
||||
timeoutSeconds: 2
|
||||
periodSeconds: 5
|
||||
failureThreshold: 5
|
||||
EOF
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- '/root/.m2/**/*'
|
||||
@@ -11,8 +11,8 @@ steps:
|
||||
name: 'gcr.io/cloud-builders/mvn'
|
||||
args: [
|
||||
'clean',
|
||||
'package',
|
||||
'-Dmaven.test.skip=true'
|
||||
'package'
|
||||
# '-Dmaven.test.skip=true'
|
||||
]
|
||||
# waitFor: ['test']
|
||||
### docker Build
|
||||
@@ -102,4 +102,4 @@ options:
|
||||
env:
|
||||
# # location/name of GKE cluster (used by all kubectl commands)
|
||||
- CLOUDSDK_COMPUTE_ZONE=asia-northeast1-a
|
||||
- CLOUDSDK_CONTAINER_CLUSTER=cluster-1
|
||||
- CLOUDSDK_CONTAINER_CLUSTER=standard-cluster-1
|
||||
@@ -1,13 +1,9 @@
|
||||
apiVersion: apps/v1
|
||||
apiVersion : apps/v1beta1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: product
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: product
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
@@ -15,6 +11,6 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: product
|
||||
image: 979050235289.dkr.ecr.ap-southeast-2.amazonaws.com/12st-product:${BUILD_NUMBER}
|
||||
image: myeventstormingregistry.azurecr.io/product
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
- containerPort: 8080
|
||||
@@ -2,7 +2,6 @@ apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: product
|
||||
namespace: default
|
||||
labels:
|
||||
app: product
|
||||
spec:
|
||||
@@ -10,4 +9,4 @@ spec:
|
||||
- port: 8080
|
||||
targetPort: 8080
|
||||
selector:
|
||||
app: product
|
||||
app: product
|
||||
11
pom.xml
11
pom.xml
@@ -79,6 +79,17 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- hystrix circuit breaker -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- Add Stackdriver Trace Starter -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.springframework.cloud</groupId>-->
|
||||
|
||||
@@ -3,11 +3,15 @@ package com.example.template;
|
||||
import com.example.template.config.kafka.KafkaProcessor;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
|
||||
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
|
||||
import org.springframework.cloud.stream.annotation.EnableBinding;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableBinding(KafkaProcessor.class)
|
||||
@EnableCircuitBreaker
|
||||
@EnableHystrixDashboard
|
||||
public class Application {
|
||||
|
||||
protected static ApplicationContext applicationContext;
|
||||
@@ -24,7 +28,7 @@ public class Application {
|
||||
product.setImageUrl("https://github.githubassets.com/images/modules/profile/profile-joined-github.png");
|
||||
product.setName(p);
|
||||
product.setPrice(i*10000);
|
||||
product.setStock(i*10);
|
||||
product.setStock(i*1);
|
||||
product.setImageUrl("/goods/img/"+p+".jpg");
|
||||
|
||||
// 상품 디테일 추가 - 양방향 관계
|
||||
|
||||
@@ -1,26 +1,42 @@
|
||||
package com.example.template;
|
||||
|
||||
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
|
||||
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
public class ProductController {
|
||||
|
||||
|
||||
private static final String RESPONSE_STRING_FORMAT = "product change from '%s': %d";
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Autowired
|
||||
ProductService productService;
|
||||
|
||||
private int count = 0;
|
||||
|
||||
private static final String HOSTNAME = parseContainerIdFromHostname(
|
||||
System.getenv().getOrDefault("HOSTNAME", "products"));
|
||||
|
||||
static String parseContainerIdFromHostname(String hostname) {
|
||||
return hostname.replaceAll("products-\\d+-", "");
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/product/{productId}")
|
||||
Product productStockCheck(@PathVariable(value = "productId") Long productId) {
|
||||
|
||||
// System.out.println("productStockCheck call");
|
||||
// try {
|
||||
// Thread.sleep(500);
|
||||
// } catch (InterruptedException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
System.out.println("productStockCheck call");
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return this.productService.getProductById(productId);
|
||||
}
|
||||
@@ -31,4 +47,35 @@ public class ProductController {
|
||||
return this.productService.save(data);
|
||||
}
|
||||
|
||||
|
||||
@PatchMapping("/product/{productId}")
|
||||
@HystrixCommand(
|
||||
fallbackMethod = "certifyFallBack",
|
||||
commandProperties = {
|
||||
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"), // 기본 타임아웃
|
||||
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"), // 서킷브레이커가 작하게될 에러 퍼센트
|
||||
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"), // 서킷브레이커가 열리기 위한 최소 요청조 (10초간 19번이 실패해도 서킷은 오픈 안됨)
|
||||
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000") } // 서킷브래이커가 열렸을때 얼마나 유지할지
|
||||
)
|
||||
ResponseEntity<String> fakeProductPatch(@PathVariable(value = "productId") Long productId, @RequestBody String data) {
|
||||
|
||||
count++;
|
||||
logger.info(String.format("product start from %s: %d", HOSTNAME, count));
|
||||
|
||||
// try {
|
||||
// Thread.sleep(900);
|
||||
// } catch (InterruptedException e) {
|
||||
// e.printStackTrace();
|
||||
// }
|
||||
|
||||
return ResponseEntity.ok(String.format(RESPONSE_STRING_FORMAT, HOSTNAME, count));
|
||||
}
|
||||
|
||||
ResponseEntity<String> certifyFallBack(Long productId, String data){
|
||||
|
||||
System.out.println("certifyFallBack");
|
||||
System.out.println(data);
|
||||
|
||||
return ResponseEntity.ok("시스템이 혼잡합니다!! 잠시후 다시 호출해주세요");
|
||||
}
|
||||
}
|
||||
|
||||
36
src/main/java/com/example/template/ProductOutOfStock.java
Normal file
36
src/main/java/com/example/template/ProductOutOfStock.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.example.template;
|
||||
|
||||
public class ProductOutOfStock extends AbstractEvent {
|
||||
|
||||
private String stateMessage = "재고량 바닥";
|
||||
private Long productId;
|
||||
private Long orderId;
|
||||
|
||||
public ProductOutOfStock(){
|
||||
super();
|
||||
}
|
||||
|
||||
public String getStateMessage() {
|
||||
return stateMessage;
|
||||
}
|
||||
|
||||
public void setStateMessage(String stateMessage) {
|
||||
this.stateMessage = stateMessage;
|
||||
}
|
||||
|
||||
public Long getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(Long productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public Long getOrderId() {
|
||||
return orderId;
|
||||
}
|
||||
|
||||
public void setOrderId(Long orderId) {
|
||||
this.orderId = orderId;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cloud.stream.annotation.StreamListener;
|
||||
import org.springframework.messaging.handler.annotation.Headers;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -16,6 +15,9 @@ import java.util.Optional;
|
||||
@Service
|
||||
public class ProductService {
|
||||
|
||||
@Autowired
|
||||
ProductRepository productRepository;
|
||||
|
||||
@StreamListener(KafkaProcessor.INPUT)
|
||||
public void onOrderPlaced(@Payload String message) {
|
||||
System.out.println("##### listener : " + message);
|
||||
@@ -35,7 +37,17 @@ public class ProductService {
|
||||
Product product = productOptional.get();
|
||||
product.setStock(product.getStock() - orderPlaced.getQuantity());
|
||||
|
||||
productRepository.save(product);
|
||||
if( product.getStock() < 0 ){
|
||||
System.out.println("productOutOfStock 이벤트 발생");
|
||||
ProductOutOfStock productOutOfStock = new ProductOutOfStock();
|
||||
productOutOfStock.setProductId(orderPlaced.getProductId());
|
||||
productOutOfStock.setOrderId(orderPlaced.getOrderId());
|
||||
productOutOfStock.publish();
|
||||
|
||||
}else{
|
||||
productRepository.save(product);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -45,9 +57,11 @@ public class ProductService {
|
||||
if( orderPlaced.getEventType().equals(OrderCancelled.class.getSimpleName())){
|
||||
Optional<Product> productOptional = productRepository.findById(orderPlaced.getProductId());
|
||||
Product product = productOptional.get();
|
||||
product.setStock(product.getStock() + orderPlaced.getQuantity());
|
||||
|
||||
productRepository.save(product);
|
||||
// productOutOfStock 상황이 아닐때만 재고량을 늘린다.
|
||||
if( product.getStock() - orderPlaced.getQuantity() > 0 ){
|
||||
product.setStock(product.getStock() + orderPlaced.getQuantity());
|
||||
productRepository.save(product);
|
||||
}
|
||||
}
|
||||
|
||||
}catch (Exception e){
|
||||
@@ -55,9 +69,6 @@ public class ProductService {
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
ProductRepository productRepository;
|
||||
|
||||
/**
|
||||
* 상품 조회
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user