Sangil's blog

https://github.com/ChoiSangIl Admin

DDD Domain Service와 Application Serivce의 차이가 뭘까? DEV / WEB

2023-03-11 posted by sang12


도메인 주도 설계 5장 Service (에릭에반스)

책에서 나온 예시

계좌 잔고가 일정 금액 아래로 떨어지면 고객에게 이메일을 발송하는 에플리케이션이 있다.
-> 계좌이체를 해서 잔고가 일정 금액이상 떨어지면 이메일을 발송하는것을 말하는듯…

응용 계층 ( Application Service ) 의 책임

  • 자금 이체 응용 서비스

    • 입력 내용의 암호화

    • 이체 처리를 위한 도메인 서비스로의 메시지 전송

    • 이체 확인 대기

    • 인프라스트럭처 서비스를 이용한 통지 결정

도메인 서비스의 책임

  • 자금 이체 도메인 서비스

    • 금액 인출/입금에 필요한 Account(계좌)와 Ledger(원장) 객체 간의 상호작용

    • 이체 결과 확인 정보 제공(이체 수락 여부 등)

인프라 스트럭처

  • 통지 서비스

    • 애플리케이션에서 지정한 곳으로 이메일이나 우편 등을 보냄

여기서 도메인 서비스가 왜 필요할까?

도메인 개념 가운데 객체로는 모델에 어울리지 않는 것이 있다. 필요한 도메인 기능을 Entity나 Value에서 억지로 맡게 하면 모델에 기반을 둔 객체의 정의가 왜곡되거나, 또는 무의미하고 인위적으로 만들어진 객체가 추가될 것이다.

위의 예시에서 보면 도메인 서비스에서는 Account(계좌)와 Ledger(원장) 객체에 접근을 해야 자금 이체를 할 수 있다. 결국 도메인 객체 안에서 혼자 해결될 수 없는데 도메인과 연관된 서비스의 경우 도메인 서비스에 설계가 되야 한다. 예제에서는 입력 내용을 암호화 하거나 메세지를 전송하는 것은 도메인 업무 규칙과는 상관이 없다.

도메인 서비스의 특징

  • 연산이 원래부터 Entity나 Value Object의 일부를 구성하는 것이 아니라 도메인 개념과 관련돼 있다.
    -> 해석….ㅠㅠ 도메인으로 처리할 수 없지만 도메인 개념과 관련되 있다는 이야기 인듯..?

  • 인터페이스가 도메인 모델의 외적 요소의 측면에서 정의된다.
    -> 해석….ㅠㅠ 요것도 도메인 모델에서 처리할 수 없다는 말인 거 같다.

  • 연산이 상태를 갖지 않는다

도메인 서비스를 사용하면 뭐가 좋을까?

세밀한 도메인 객체 (세밀한 도메인 객체가 뭔가요 대체)에서 지식이 새어 나오게 해서 도메인 객체의 행위를 조정하는 응용 계층을 흘러가게 할 수 있다.

→ 응용 계층(Application Service)에 도메인 규칙들이 스며드는 것을 말하는 듯

도메인 서비스를 적절히 도입하면 계층 간의 경계를 선명하게 하는 데 도움될 수 있다.

→ 응용 계층에 도메인 규칙들이 스며드는 것을 막을 수 있어서 계층 간의 경계를 더욱 선명하게 하는데 도움이 될 수 있다.

REPLY

FlyweightPattern in kotlin with ChatGPT DEV / WEB

2023-03-05 posted by sang12


세상이 무섭게 변하는거 같다.
FlyweightPattern 코틀린 예제로 보여줘~ 라고하면 뚝딱 예제가 나온다

FlyweightPattern 은 특정 객체를 사용할때 마다 생성하지 않고
이미 만들어져있거나 없으면 만들어서 리턴해주는 방법이며
Flyweight이라는 용어처럼 리소스를 세이브할 수 있는 패턴이다.
일반적으로는 예제를 보면 이해가 되겠지만 다양한 방법으로 활용할 수 있지 않을까 싶다.

Chat GPT가 만들어준 FlyweightPattern

package pattern

interface Shape {
    fun draw(x: Int, y: Int)
}

class Circle(private val color: String) : Shape {
    override fun draw(x: Int, y: Int) {
        println("Drawing a $color circle at ($x, $y)")
    }
}

class ShapeFactory {
    private val circleMap = mutableMapOf<String, Shape>()

    fun getCircle(color: String): Shape {
        var circle = circleMap[color]

        if (circle == null) {
            circle = Circle(color)
            circleMap[color] = circle
            println("Creating a $color circle")
        } else {
            println("Reusing a $color circle")
        }

        return circle
    }
}

fun main() {
    val factory = ShapeFactory()

    val redCircle1 = factory.getCircle("red")
    redCircle1.draw(10, 10)

    val blueCircle1 = factory.getCircle("blue")
    blueCircle1.draw(20, 20)

    val redCircle2 = factory.getCircle("red")
    redCircle2.draw(30, 30)

    val blueCircle2 = factory.getCircle("blue")
    blueCircle2.draw(40, 40)
}

REPLY

Spring Websocket - Kotlin을 이용한 좌석 예약 프로그램 만들기 With Vue DEV / WEB

2023-02-21 posted by sang12


Spring Websocket과 Vue를 이용하여 간단한 좌석 예약 프로그램을 만들어 보겠습니다~ 

간단한 데모성 프로젝트여서 서비스로직같은 거는 없고 웹소켓을 연결하고 사용하는 것만 참고하시면 좋을거 같습니다~ Github 예제소스도 첨부해놨습니다!

-소스코드 참고

bakcend: https://github.com/ChoiSangIl/sit-down-monkey-websocket
front: https://github.com/ChoiSangIl/websocket-front

Dependancy 

implementation("org.springframework.boot:spring-boot-starter-websocket")
implementation("org.springframework.boot:spring-boot-starter-web")


Spring 설정

@Configuration
@EnableWebSocketMessageBroker
class WebSocketBrokerConfig: WebSocketMessageBrokerConfigurer {
    override fun configureMessageBroker(registry: MessageBrokerRegistry) {
        registry.enableSimpleBroker("/seat")
    }
    override fun registerStompEndpoints(registry: StompEndpointRegistry) {
        registry.addEndpoint("/sit-down-monkey")
            .setAllowedOrigins("http://localhost:8081")
            .withSockJS()
    }
}

Controller

simpleMessagingTemplate을 주입 받아서 원하는 Queue에 push 할 수 있다


@RestController
class ReservationController (
    val seatReservationRepository: SeatReservationRepository,
    val simpleMessagingTemplate: SimpMessagingTemplate
){
    @RequestMapping("/api/v1/reservation/{id}")
    fun reservationSeat(@PathVariable("id") id: Int){
        try{
            seatReservationRepository.reserveSeat(id)
            simpleMessagingTemplate.convertAndSend("/seat", Seat(id, true))
            println("좌석번호 $id 예약 되었습니다")
        }catch (e: Exception){
            e.printStackTrace()
        }
    }

    @RequestMapping("/api/v1/reservation/cancel/{id}")
    fun cancelReservation(@PathVariable("id") id: Int){
        seatReservationRepository.cancelReservation(id)
        simpleMessagingTemplate.convertAndSend("/seat", Seat(id, false))
        println("좌석번호 $id 예약 취소 되었습니다")
    }
}

data class Seat(
    val id: Int,
    val hasReservation: Boolean
)

persistence

object SeatMemoryDatabase{
    private const val MAX_SEAT_SIZE = 100
    private val seat: Array = Array(MAX_SEAT_SIZE) { false }

    fun reserveSeat(id: Int){
        if(seat[id]) throw Exception("이미 예약되었습니다")
        seat[id] = true
    }

    fun cancelReservation(id: Int){
        if(!seat[id]) throw Exception("이미 취소 되었습니다.")
        seat[id] = false
    }
}

@Repository
class SeatReservationRepository {
    fun reserveSeat(id: Int){
        SeatMemoryDatabase.reserveSeat(id)
    }

    fun cancelReservation(id: Int){
        SeatMemoryDatabase.cancelReservation(id)
    }
}

VUE

<template>
  <div>
    <template v-for="(item, index) in seatList" :key="item">
      <template v-if="index % 10 == 0"> <br/> </template>
      <v-chip v-if="item" @click="cancle(index)">
        {{ item }}
      </v-chip>

      <v-chip v-else-if="item == false" color="green" @click="reservation(index)">
        {{ item }}
      </v-chip>
    </template>
    
  </div>
</template>

<script>
import Stomp from 'webstomp-client'
import SockJS from 'sockjs-client'
import axios from 'axios';

export default {
  name: 'App',
  data() {
    return {
      seatList: Array.from({length: 100}, () => false),
      name: 'kukaro',
      age: 26,
    }
  },
  created() {
    const serverURL = "http://localhost:8080/sit-down-monkey"
    let socket = new SockJS(serverURL);
    this.stompClient = Stomp.over(socket);
    
    console.log(`소켓 연결을 시도합니다. 서버 주소: ${serverURL}`)
    let ref = this
    this.stompClient.connect(
        {},
        frame => {
          this.connected = true;
          console.log('소켓 연결 성공', frame);
            this.stompClient.subscribe("/seat", res => {
                console.log('구독으로 받은 메시지 입니다.', res.body)
                let seat = JSON.parse(res.body)
                ref.seatList[seat['id']] = seat['hasReservation']
            });
        },
        error => {
          console.log('소켓 연결 실패', error);
          this.connected = false;
        } 
    );        
  },
  methods:{
    reservation(seatId) {
      axios.get( "/api/v1/reservation/" + seatId)
        .then((response) => {
          console.log(response)
        })
        .catch((error)=>{
            console.log(error)
            alert("서버가 아파요 ㅜㅜ")
        })
    },
    cancle(seatId){
      if(confirm(seatId + "취소하시겠습니까?")){
        axios.get( "/api/v1/reservation/cancel/" + seatId)
        .then((response) => {
          console.log(response)
        })
        .catch((error)=>{
            console.log(error)
            alert("서버가 아파요 ㅜㅜ")
        })
      }
    }
  }
}
</script>

REPLY