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를 만들어낸다
- Future [map]
- future 안에 있는 요소에 특정 함수를 수행시켜 새로운 future를 만들어낸다
- 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로 변환하는데 유용하게 쓰임