아카에서 상태를 안전하게 공유할 때 쓰는 도구.
내부적으로는 액터를 사용하는데, 상태를 바꿀 때 좀 더 편하게 만들어 준다.
에이전트를 만들 때는 초기 값을 줘야 한다. 정수를 다루는 에이전트를 만드는 예시이다.
import akka.agent.Agent
import akka.actor.ActorSystem
import scala.concurrent.ExecutionContext.Implicits.global
implicit val system = ActorSystem("AgentSystem")
val agent = Agent(0) // 초기값 0으로 에이전트 생성
상태를 바꿀 때는 send 또는 alrter메서드를 사용한다.
두 메서드의 차이점은, 대기열에 어떻게 작업을 추가하는지에 있다.
send: 새로운 상태 변경 작업을 에이전트의 내부 대기열에 추가. 이 작업은 다른 상태 변경 작업들과 순서대로 처리되고, 작업이 완료되면 그 Future가 완료됨.
alter: 새로운 상태 변경 작업을 에이전트의 내부 대기열에 추가하지만, 작업이 실제로 처리되기 전에 현재 상태를 먼저 체크.
둘 다 상태 변경 작업을 비동기적으로 대기열에 추가하고, 작업이 완료되면 Future가 완료되는 구조. 다만 내부적으로 어떻게 작업을 대기열에 추가하고 처리하는지가 조금 다를 뿐이다.
// send 메서드를 사용하여 상태 변경
val result1: Future[Int] = agent.send(_ + 1) // 결과는 Future[Int], 값은 6
// alter 메서드를 사용하여 상태 변경
val result2: Future[Int] = agent.alter(_ * 2) // 결과는 Future[Int], 값은 12 (만약 이전 연산이 완료된 상태라면)
상태를 읽기 위해서는 agent() 이 방식으로 호출하면 된다. 이게 현재 상태를 반환해준다.
val current = agent() // 현재 상태 읽기
alter 를 쓰면 Future 가 반환되기 때문에 이걸 이용해서 여러 작업을 연결할 수 있다.
val future3 = agent.alter(_ + 3).flatMap { _ =>
agent.alter(_ * 2)
}
위의 코드에서는 먼저 3을 더한 다음 그 결과에 2를 곱하게 된다. 모든 작업은 비동기적으로 처리되고, 최종 결과는 future3 에 담기게 된다.
상태가 간단한 정수가 아니라, 복잡한 객체일 수도 있다.
예를 들어 아래와 같이 Map을 상태로 가지는 에이전트를 만들 수 있다.
val mapAgent = Agent(Map.empty[String, Int])
mapAgent.send(_ + ("key" -> 1))
이렇게 하면 Map에 'key' 라는 키와 값 1을 추가 할 수 있다.
에이전트는 내부적으로 액터를 사용한다.
액터는 액터 시스템에 의존적인 컴포넌트라서 액터를 사용하기 위해서는 반드시 액터 시스템이 있어야 한다.
에이전트의 목표는 '안전한 상태 공유' 인데, 이 안전성을 보장하기 위해 액터 모델을 사용하고 있다.
에이전트를 안전히 닫기 위해서는
system.terminate()
이렇게 입력해야 한다.