콘텐츠로 이동

2023 03 21

2023-03-21

비동기 리팩터링

  • 기존 코드
    // Controller
    def sendMessge() = Action(parse.json[MessageRequest]) { implicit request =>
      val messageRequest: MessageRequest = request.body
        alimtalkService.sendMessage(messageRequest)
        Ok
    }
    
    // Service
    def sendMessage(messageRequest: MessageRequest): Unit = {
      val template = Template.select(messageRequest.templateType)
      szoneUserDao.getSzoneUsers(messageRequest.ids).map {
        szoneUsers => szoneUsers.foreach(user => sendAlimtalk(template, user))
      }
    }
    
    private def sendAlimtalk(template: Template, user: SzoneUser): Unit = {
      val request = AlimtalkRequest.makeRequest()
      httpClient.post[AlimtalkRequest, AlimtalkResponse](URL, request)
      messageHistoryDao.saveMessageHistory(request.number, user)
    }
    
  • 비동기 ON
    • Future와 Action.async를 통한 리팩터링
    • Future의 다양한 api를 활용해 지지고 볶을 것
      // Controller
      def sendMessage() = Action.async(parse.json[MessageRequest]) { implicit request =>
        val messageRequest: MessageRequest = request.body
          alimtalkService.sendMessage(messageRequest).map(_ => Ok)
      }
      
      // Service
      def sendMessage(messageRequest: MessageRequest): Future[Seq[Unit]] = {
        val template = Template.select(messageRequest.templateType)
        szoneUserDao.getSzoneUsers(messageRequest.ids).flatMap {
          szoneUsers =>
            Future.sequence(szoneUsers.map {
              user => sendAlimtalk(template, user)
            })
        }
      }
      
      private def sendAlimtalk(template: Template, user: SzoneUser): Future[Unit] = {
        val request = AlimtalkRequest.makeRequest()
        httpClient.post[AlimtalkRequest, AlimtalkResponse](URL, request).map { _ =>
          messageHistoryDao.saveMessageHistory(request.number, user)
        }
      }
      
  • 어라 복잡하네? 한번 뜯어보자
    // Service
    def sendMessage(messageRequest: MessageRequest): Future[Seq[Unit]] = {
      val template = Template.select(messageRequest.templateType)
      szoneUserDao.getSzoneUsers(messageRequest.ids).flatMap {
        szoneUsers =>
          Future.sequence(szoneUsers.map {
            user => sendAlimtalk(template, user)
          })
      }
    }
    
    def sendMessage(messageRequest: MessageRequest): Future[Seq[Unit]] = {
      val template = Template.select(messageRequest.templateType)
    
      val sendAlimtalkToAll: Seq[SzoneUser] => Seq[Future[Unit]] = {
        szoneUsers => szoneUsers.map {
          szoneUser => sendAlimtalk(template, szoneUser)
        }
      }
    
      val convertToFuture: Seq[Future[Unit]] => Future[Seq[Unit]] = {
        test => Future.sequence(test)
      }
    
      szoneUserDao.getSzoneUsers(messageRequest.ids).map(sendAlimtalkToAll).flatMap(convertToFuture)
    }
    
  • Future [flatMap]
    • future 안에 있는 요소에 특정 함수를 수행시켜 새로운 future를 만들어낸다
      def flatMap[S](f: T => Future[S]): Future[S] = transformWith {
        t => 
          if (t.isInstanceOf[Success[T]]) f(t.asInstanceOf(Success[T]).value)
          else this.asInstanceOf[Future[S]]
      }
      
  • Future [map]
    • future 안에 있는 요소에 특정 함수를 수행시켜 새로운 future를 만들어낸다
      def map[S](f: T => S): Future[S] = transform(_ map f)
      
  • Future map vs flatMap
    • 둘의 가장 큰 차이는 "파라미터로 받는 함수의 타입", "계산의 결과값 타입"
    • map은 함수가 future를 반환하지 않아
    • flatMap은 함수가 future를 반환해야해
      class MapFlatMap extends AnyFunSuite {
      
          test("future의 map과 flatMap의 차이를 이해한다") {
              val futureMap: Future[Int] = Future.successful(42)
              val mapper: (Int) => String = x => s"To String${x}"
              val transformed: Future[String] = futureMap.map(mapper)
      
              val futureFlatMap: Future[Int] = Future.successful(42)
              val divide: (Int, Int) => Future[Int] = (a, b) => {
                  if (b == 0) Future.failed(new IllegalArgumentException("0으로 나누면 안댐"))
                  else Future.successful(a / b)
              }
              val transformedFlatMap = futureFlatMap.flatMap(x => divide(x, 2))
          }
      }
      
  • Future [Future.sequence]
    • Future.traverse의 간단한 버전
    • 비동기/넌블러킹 방식으로 IterableOnce[Future[A]] => Future[IterableOnce[A]]로 변경
    • 많~~은 Future를 하나의 Future로 변환하는데 유용하게 쓰임