분석 설계

Event Storming 결과

MSAez를 사용한 event stroming결과 아래와 같이 설계

캡처

헥사고날 아키텍쳐

12

구현

DDD 의 구현

MSAez로 구현한 Aggregate단위로 Entity를 선언 후, 구현 진행.

package shop;

import javax.persistence.*;
import org.springframework.beans.BeanUtils;
import shop.external.Payment;

import java.util.List;

@Entity
@Table(name="Order_table")
public class Order {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    private Long productId;
    private Long qty;
    private String orderStatus;
    private Long orderId;

    @PostPersist
    public void onPostPersist()
    {
        Ordered ordered = new Ordered();
        BeanUtils.copyProperties(this, ordered);
        ordered.setOrderStatus("Order");
        ordered.setOrderId(this.getOrderId());
        ordered.publishAfterCommit();

        shop.external.Payment payment = new shop.external.Payment();
        System.out.println("결제 이벤트 발생");
        payment.setId(this.getOrderId());
        payment.setStatus("Paid");
        OrderApplication.applicationContext.getBean(shop.external.PaymentService.class)
                .pay(payment);

    }
    @PreUpdate
    public void onPrePersist(){


        OrderCanceled orderCanceled = new OrderCanceled();
        BeanUtils.copyProperties(this, orderCanceled);
        if(orderCanceled.getOrderStatus().equals("Cancel"))
        {
            orderCanceled.setOrderStatus(this.getOrderStatus());
            orderCanceled.publishAfterCommit();
        }
        else
        {
            PaymentRequested paymentRequested = new PaymentRequested();
            BeanUtils.copyProperties(this, paymentRequested);
            paymentRequested.publishAfterCommit();



            //orderCanceled.publishAfterCommit();

            //Following code causes dependency to external APIs
            // it is NOT A GOOD PRACTICE. instead, Event-Policy mapping is recommended.

            //shop.external.Cancellation cancellation = new shop.external.Cancellation();
            // mappings goes here

            //OrderApplication.applicationContext.getBean(shop.external.CancellationService.class)
            //    .cancel(cancellation);
        }



    }


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
    public Long getProductId() {
        return productId;
    }

    public void setProductId(Long productId) {
        this.productId = productId;
    }
    public Long getQty() {
        return qty;
    }

    public void setQty(Long qty) {
        this.qty = qty;
    }

    public void setOrderStatus(String orderStatus){this.orderStatus = orderStatus;}
    public String getOrderStatus(){return orderStatus;}

    public Long getOrderId(){return orderId;}
    public void setOrderId(Long orderId){this.orderId = orderId;}
}

REST API에서의 테스트를 통하여 구현내용이 정상적으로 동작함을 확인.

3

4

동기식 호출(Request 방식의 아키텍쳐)

Order 내에 아래와 같은 FeignClient 선언

package shop.external;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.Date;

@FeignClient(name="pay", url="${api.url.pay}")
//@FeignClient(name="pay", url="http://pay:8080")
public interface PaymentService {

    @RequestMapping(method= RequestMethod.GET, path="/payments")
    public void pay(@RequestBody Payment payment);

}

@PoosePersist에서 이벤트 처리 수행

@PostPersist
    public void onPostPersist()
    {
        Ordered ordered = new Ordered();
        BeanUtils.copyProperties(this, ordered);
        ordered.setOrderStatus("Order");
        ordered.setOrderId(this.getOrderId());
        ordered.publishAfterCommit();

        shop.external.Payment payment = new shop.external.Payment();
        System.out.println("결제 이벤트 발생");
        payment.setId(this.getOrderId());
        payment.setStatus("Paid");
        OrderApplication.applicationContext.getBean(shop.external.PaymentService.class)
                .pay(payment);

    }

Pay서비스와 Order 서비스가 둘 다 돌아가고 있을 때에는 Order서비스에 아래와 같이 수행 하여도 이상 없음.

5

Pay 서비스를 내린 후, Order 서비스만 돌아가고 있는 상태에서는 Order 서비스에 아래와 같이 수행시 이상 발생.

6

비동기식 호출(Pub/Sub 방식의 아키텍쳐)

Payment.java내에서 아래와 같이 서비스 Publish 구현

    @PostUpdate
    public void onPostUpdate()
    {
        Paid paid = new Paid();
        BeanUtils.copyProperties(this, paid);
        paid.publishAfterCommit();

    }

Delivery 서비스 내 Policy Handler에서 아래와 같이 Sub 구현

@StreamListener(KafkaProcessor.INPUT)
    public void wheneverPaid_Ship(@Payload Paid paid){

        if(paid.isMe()){
            System.out.println("##### listener Ship : " + paid.toJson());
            Delivery delivery = new Delivery();
            delivery.setDeliveryStatus("ordered");
            System.out.println("Delivery start");

            deliveryRepository.save(delivery);
        }

    }

Pay 서비스와 Delivery 서비스가 둘 다 동시에 돌아가고 있을때 Pay 서비스 실행시 이상 없음. 8

Pay 서비스는 실행한 채, Delivery 서비스를 내린 후, Pay 서비스를 실행하여도 이상 없이 동작 가능. 9

Gateway

7

Gateway 서비스 실행 상태에서 8088과 8084로 각각 서비스 실행하여도 동일하게 Pay 서비스 실행됨 확인.

8

10

CQRS

viewer를 별도로 구현하여 아래와 같이 view 실행 결과 확인 가능

13

Poly Glot

다른 서비스와 달리 Order 서비스는 h2db 가 아닌 hsqldb 를 사용

11

운영

CI / CD

14

15

16

17

18

SLA 준수

Liveness Test

deployment.yml파일 내용 가운데 http -> tcpSocket변경, 8080 -> 8081로 변경 후, 서비스 확인 수행

20

18

kubectl describe deploy order 명령어로 확인시 아래와 같은 결과 확인.

21

Readness Test

아무런 제약 주지 않은채 readness만 적용할 경우, availability 100% 확인.

22

delivery 서비스의 readness를 주석처리 한 후, availability 확인.

23

Configmap

Configmap 생성 후, 아래와 같이 적용 수행

24

25

26

HPA

27 28 29

31 32 33