본문 바로가기

Programmer Jinyo/Scala & FP

AKKA(아카)with Scala 튜토리얼 01


투명한 기부를 하고싶다면 이 링크로 와보세요! 🥰 (클릭!)

바이낸스(₿) 수수료 평생 20% 할인받는 링크로 가입하기! 🔥 (클릭!)

Typed AKKA가 새로 나와서 이 시리즈 글은 번역하다 말았습니다.

여튼 이 튜토리얼도 좋은 튜토리얼이니 의지가 있으시다면 따라가보셔도 좋을 것 같습니다.

2019/12/01 - [Programmer Jinyo/Scala & AKKA] - AKKA(아카)with Scala 튜토리얼 01

2019/12/01 - [Programmer Jinyo/Scala & AKKA] - AKKA(아카)with Scala 튜토리얼 02

2019/12/02 - [Programmer Jinyo/Scala & AKKA] - AKKA(아카)with Scala 튜토리얼 03 (Routers)


왜 한국에는 아카-스칼라 튜토리얼 자료가 찾아볼래야 찾을 수가 없을까?

나는 눈물이 난다...

 

http://allaboutscala.com/scala-frameworks/akka/

https://github.com/nadimbahadoor/learn-akka

 

위 링크를 이제 차곡 차곡 번역/요약 할 거다.

대상 독자는 scala를 어느정도 아는 독자..

 

젭알.... 이번 링크는 똥이 아니길 빌면서

 

일단 sbt 환경의 intelliJ IDEA를 사용할 것이다.

 

가즈아.

 

아카가 무엇인지와 아카의 생태계를 이루고 있는 큼직큼직한 모듈들에 대해 설명한다. 어차피 앞으로 다룰거라서 특별히 번역은 하지 않았다. 가볍게 읽어보자.

 

What is Akka?

Akka is a suite of modules which allows you to build distributed and reliable systems by leaning on the actor model.

 

The actor model puts emphasis on avoiding the use of locks in your system, in favour of parallelism and concurrency. As a result, actors are those 'things' that would 'react' to messages, and perhaps run some computation and/or respond to another actor via message passing. The actor model has been around for a while, and was certainly made popular by languages such as Erlang.

 

 

 

Akka Actor:

This module introduces the Actor System, functions and utilities that Akka provides to support the actor model and message passing. For additional information, you can refer to the official Akka documentation on Actors.

 

Akka HTTP: 

As the name implies, this module is typically best suited for middle-tier applications which require an HTTP endpoint. As an example, you could use Akka HTTP to expose a REST endpoint that interfaces with a storage layer such as a database. For additional information, you can refer to the official Akka documentation on Akka HTTP.

 

Akka Streams:

This module is useful when you are working on data pipelines or even stream processing. For additional information, you can refer to the official Akka documentation on Akka Streams.

 

Akka Networking:

This module provides the foundation for having actor systems being able to connect to each other remotely over some predefined network transport such as TCP. For additional information, you can refer to the official Akka documentation on Akka Networking.

 

Akka Clustering:

This module is an extension of the Akka Networking module. It is useful in scaling distributed applications by have actors form a quorum and work together by some predefined membership protocol. For additional information, you can refer to the official Akka documentation on Akka Clustering.

 

 

 

 

개발환경

sbt 1.3.4

scala 2.12.4

Akka 2.5.12

 

Create -> sbt -> 버전설정 후 새 프로젝트 생성

 

 

여기는 환경설정 하는 곳이다. JAVA 프로젝트 할 때 MAVEN같은 곳이랄까?

 

 

scalaVersion := "2.12.4"

libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-actor" % "2.5.12",
  "com.typesafe.akka" %% "akka-testkit" % "2.5.12" % Test
)

 

여기에 프로젝트에서 사용할 라이브러리들의 버전을 적어주자.

 

 

 

 

Actor System Introduction

 

이 튜토리얼에서 당신은 당신의 첫 Akka ActorSystem을 만들 것이다. 이 튜토리얼에선 ActorSystem을 그냥 결론적으로 actors를 사용할 수 있게 담고있어주는 블랙 박스라고 생각해도 된다.

 

복습, actor model  message passing 을 1급 객체 시민으로 놓고있다. 만약 액터 모델이 여전히 애매모호해도 괜찮다. 이어지는 튜토리얼에서는 액터가 이해하고 반응할 수 있는 프로토콜을 디자인하는 방법을 보여줄것이다.

 

AKKA를 사용해서 우리의 ActorSystem을 정의하는것은 쉽다. 단순히 akka.actor.ActorSystem의 인스턴스를 생성하기만 하면 된다. 아래 코드에서 우리는 우리의 ActorSystem의 이름을 DonutStoreActorSystem이라고 붙일 것이다.

println("Step 1: Create an actor system")
val system = ActorSystem("DonutStoreActorSystem")

이 튜토리얼의 목적에 따라 지금은 ActorSystem으로는 많은 것을 하지 않을 것이다 :( 하지만 당신이 terminate() method를 사용해서 이것을 소멸시킬 수 있다는 것을 보여주는것은 중요하다. 명백히 언젠가는 이 연결을 끊고 싶을 상황이 올 것이기 때문이다.

println("\nStep 2: close the actor system")
val isTerminated = system.terminate()

완벽히 액터 시스템이 꺼졌다는 것을 확인하기 위해서Future onComplete() 를 등록할 것이다.

http://allaboutscala.com/tutorials/chapter-9-beginner-tutorial-using-scala-futures/#non-blocking-future-result

에 scala의 future관련 튜토리얼이 있다고 한다. ㄷㄷ언제 다읽읍니까... 존내 길다;; )

 

import scala.util.{Failure, Success}
//Failure Success가 여러 종류라서 굳이 언급.


println("\nStep 3: Check the status of the actor system")
isTerminated.onComplete {
  case Success(result) => println("Successfully terminated actor system")
  case Failure(e)     => println("Failed to terminate actor system")
}

Thread.sleep(5000)

( 짬에서 나오는 바이브로 관심법 해보면 함수 호출이 종료된 이후에 성공 / 실패에 따라 다른 행동 하게 하는 뭔가가 있나보다.. )

 

+ 이렇게 그냥 튜토리얼 1이 끝나버린다; ㅋㅋㅋㅋ 너무 스무스해서 눈치 못챌뻔

 

 

Tell Pattern

 

이 튜토리얼에서는 우리의 첫 AKKA Actor을 생성 해 볼 것이고 어떻게 Actor과 통신하기 위해 간단한 message passing protocol을 디자인 하는지 보일 것이다. AKKA는 다양한 상호작용 패턴을 제공하고 있으며 우리는 이번 튜토리얼에서 Tell Pattern이라고 하는 패턴을 사용한다. 이 패턴은 액터에게 메시지를 보내지만 답장을 기대하지 않을 때 유용하다. 결과적으로 이런 속성 때문에 이것은 "fire and forget"이라고 불린다.

 

이전 ActorSystem 입문 튜토리얼에 따라 ActorSystem을 인스턴스화하고 DonutStoreActionSystem이라는 이름을 줄 것이다.

 

println("Step 1: Create an actor system")
val system = ActorSystem("DonutStoreActorSystem")

 

다음으로, 간단한 메시지 전달 프로토콜을 디자인하고 case class를 사용하여 메시지를 캡슐화한다. 따라서 우리는 Info라는 case class를 만들며 이것은 String타입의 name을 가지고 있다.

(케이스 클래스는 그냥 클래스에서 조금 더 기능을 확장한 클래스이다. 자동으로 public 멤버 변수를 만들어주며 객체의 정보를 알 수 있게 해주는 toString 등등이 자동으로 추가된다.)

 

println("\nStep 2: close the actor system")
val isTerminated = system.terminate()

 

AKKA Actor을 만드는 일은 굉장히 쉽다. Actor trait을 상속받기만 하면 된다. AKKA는 또한 actor을 위한 logging 유틸리티를 가지고 있어서 ActorLogging trait을 같이 상속받으면 추가할 수 있게 된다. 

 

액터 안에서 우리가 집중해야 할 부분은 receive 메서드이다. receive method는 당신의 액터가 어떤 메시지나 프로토콜에 대해서 반응해야 할지에 대한 지침같은 것이다. (그니까 걍 액터한테 명령하는 것을 받아들여서 행동하는 함수)

println("\nStep 3: Define DonutInfoActor")
class DonutInfoActor extends Actor with ActorLogging {

  import Tutorial_02_Tell_Pattern.DonutStoreProtocol._ // 이 부분은 어디에다가 코딩했냐에 따라 다름

  def receive = {
    case Info(name) =>
      log.info(s"Found $name donut")
  }
}

이렇게 튜토리얼 따라 하는 중..이었으나 이러면 안됐었다.. 어케어케 완성한 코드는 아래에 dream

현재로서는, DonutStoreActorSystem이 비어있다. 이제 계속해서 actorOf () 메소드를 사용하여 DonutInfoActor를 만든다.

println("\nStep 4: Create DonutInfoActor")
val donutInfoActor = system.actorOf(Props[DonutInfoActor], name = "DonutInfoActor")

(Props는 actor을 만들기 위한 기능들이 들어간 wrapper 같은 것이라고 생각하면 된다.)

 

Akka Tell Pattern을 사용하여 DonutInfoActor에 메시지를 보내려면 bang 연산자 !아래와 같이 사용할 수 있다. 

println("\nStep 5: Akka Tell Pattern")
import DonutStoreProtocol._
donutInfoActor ! Info("vanilla")

이제 terminate() 메서드를 사용해서 끄자.

println("\nStep 6: close the actor system")
val isTerminated = system.terminate()

 

 

 

* 이제 tell 패턴이 끝났는데, 이거 실행하는 방법이 좀 애매하다; (내가 못 찾은걸수도 있는데)

나는 주어진 프로그램 코드들을 아래와 같이 쑤셔넣은 후 sbt run 명령을 통해 실행했다. (혹은 도넛가게액터시스템 클래스를 scala로 실행해도 동작은 하더라)

 

import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import DonutStoreProtocol._



object DonutStoreActorSystem extends App{
  println("Step 1: Create an actor system")
  val system = ActorSystem("DonutStoreActorSystem")

  println("\nStep 4: Create DonutInfoActor")
  val donutInfoActor = system.actorOf(Props[DonutInfoActor], name = "DonutInfoActor")

  println("\nStep 5: Akka Tell Pattern")
  donutInfoActor ! Info("vanilla")

  println("\nStep 6: close the actor system")
  val isTerminated = system.terminate()
}

//println("\nStep 2: Define the message passing protocol for our DonutStoreActor")
object DonutStoreProtocol {
  case class Info(name: String)
}

//println("\nStep 3: Define DonutInfoActor")
class DonutInfoActor extends Actor with ActorLogging {
  def receive = {
    case Info(name) =>
      log.info(s"Found $name donut")
  }
}

폴더구조

그리고 튜토리얼 호흡이 짧아서 따라하기가 편하기는 하다. 굿굿

 

 

 

Ask Pattern

 

이 튜토리얼에서는 Ask Pattern이라고 이름 붙여진 AKKA액터들에 대한 또다른 상호작용을 볼 것이다.

이 패턴을 사용하면 액터에게 메시지를 보내고 응답을받을 수 있다. 이전 튜토리얼에서의 Akka Tell Pattern을 사용하면 엑터로부터 응답을받지 못한다.

먼저 ActorSystem을 작성하는 것으로 시작함. 이전 예제와 유사하게 이름을 DonutStoreActorSystem으로 정하자.

 

println("Step 1: Create an actor system")
val system = ActorSystem("DonutStoreActorSystem")
// 왜 자꾸 도넛을 찾는걸까

이제,  message passing protocol 을 이전 예제와 동일하게 유지한다. 우리의 메시지는 Info라는 case class로 표시되며  String 유형의 단일 변수 name을 갖는다.

println("\nStep 2: Define the message passing protocol for our DonutStoreActor")
object DonutStoreProtocol {
    case class Info(name: String)
}

Tell Pattern 예제와 유사하게 Akka 액터 만들기는 Actor trait을 확장하고 receive 메소드를 구현하는 것만큼 쉽다. 수신 본문 내에서 sender 메소드를 사용하여 메시지가 발생한 source에 회신한다. 아래의 간단한 예에서는 Info 메시지의 name 속성이 vanilla 인 경우 true를 반환하고 name 속성의 다른 모든 값에 대해 false를 반환한다.

println("\nStep 3: Create DonutInfoActor")
class DonutInfoActor extends Actor with ActorLogging {
    import Tutorial_03_Ask_Pattern.DonutStoreProtocol._

    def receive = {
      case Info(name) if name == "vanilla" =>
        log.info(s"Found valid $name donut")
        sender ! true

      case Info(name) =>
        log.info(s"$name donut is not supported")
        sender ! false
    }
}

DonutStoreActorSystem 내에서 DonutInfoActor를 만들려면 아래와 같이 actorOf () 메서드를 사용한다.

println("\nStep 4: Create DonutInfoActor")
val donutInfoActor = system.actorOf(Props[DonutInfoActor], name = "DonutInfoActor")

Akka Ask Pattern을 사용하려면 ? 를 사용해야 한다. 이전에는, Akka Tell Pattern의 경우, ! 연산을 사용하였다. Ask Pattern은 future를 반환하고, 이 예제를 이해하기 위해 DonutInfoActor의 응답을 for comprehension을 통해 출력한다. Scala에서 Futures를 처음 사용하는 경우 Futures 튜토리얼(http://allaboutscala.com/tutorials/chapter-9-beginner-tutorial-using-scala-futures/#futures-introduction)을 보면서 asynchronous non-blocking 연산을 사용하는 방법을 알아보아라. (는 영어임 ㅈㅅ ㅋ)

println("\nStep 5: Akka Ask Pattern")
import DonutStoreProtocol._
import akka.pattern._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
implicit val timeout = Timeout(5 second)

val vanillaDonutFound = donutInfoActor ? Info("vanilla")
for {
  found <- vanillaDonutFound
} yield (println(s"Vanilla donut found = $found"))

val glazedDonutFound = donutInfoActor ? Info("glazed")
for {
  found <- glazedDonutFound
} yield (println(s"Glazed donut found = $found"))

Thread.sleep(5000)

마지막으로 terminate() 메소드를 호출하여 액터 시스템을 종료한다.

println("\nStep 6: Close the actor system")
val isTerminated = system.terminate()

 

비동기 프로그램 답게 나에게는

 

[INFO] [12/01/2019 02:21:47.299] [DonutStoreActorSystem-akka.actor.default-dispatcher-3] [akka://DonutStoreActorSystem/user/DonutInfoActor] Found valid vanilla donut
[INFO] [12/01/2019 02:21:47.302] [DonutStoreActorSystem-akka.actor.default-dispatcher-2] [akka://DonutStoreActorSystem/user/DonutInfoActor] glazed donut is not supported
Vanilla donut found = true
Glazed donut found = false 

 

이렇게 결과가 나왔다.

 

본 글에서는

 

[INFO] [06/22/2018 21:37:20.381] [DonutStoreActorSystem-akka.actor.default-dispatcher-4] [akka://DonutStoreActorSystem/user/DonutInfoActor] Found valid vanilla donut

Vanilla donut found = true

[INFO] [06/22/2018 21:37:20.391] [DonutStoreActorSystem-akka.actor.default-dispatcher-2] [akka://DonutStoreActorSystem/user/DonutInfoActor] glazed donut is not supported

Glazed donut found = false

 

이렇게 나온다.

 

 

import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
import akka.util.Timeout
import akka.pattern._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ // 5 second의 second를 위한 import
import DonutStoreProtocol._

object DonutStoreActorSystem extends App{
  println("Step 1: Create an actor system")
  val system = ActorSystem("DonutStoreActorSystem")
  println("\nStep 4: Create DonutInfoActor")
  val donutInfoActor = system.actorOf(Props[DonutInfoActor], name = "DonutInfoActor")

  implicit val timeout = Timeout(5 second)
``
  val vanillaDonutFound = donutInfoActor ? Info("vanilla")
  for {
    found <- vanillaDonutFound
  } yield (println(s"Vanilla donut found = $found"))

  val glazedDonutFound = donutInfoActor ? Info("glazed")
  for {
    found <- glazedDonutFound
  } yield (println(s"Glazed donut found = $found"))

  Thread.sleep(5000)
}

//println("\nStep 2: Define the message passing protocol for our DonutStoreActor")
object DonutStoreProtocol { // VO랑 비슷한듯?
  case class Info(name: String)
}

//println("\nStep 3: Create DonutInfoActor")
class DonutInfoActor extends Actor with ActorLogging {
  import DonutStoreProtocol._

  def receive = {
    case Info(name) if name == "vanilla" =>
      log.info(s"Found valid $name donut")
      sender ! true

    case Info(name) =>
      log.info(s"$name donut is not supported")
      sender ! false
  }
}

내가 완성한 코드는 위와 같다.

 

 

요까지 하고 차차 나머지도 해보도록 하자 .