1. 序章

このチュートリアルでは、Akkaアクターを検出する方法を見ていきます。 まず、2つのアクターを登録し、直接のアクター参照を使用する代わりに、ServiceKeysを使用してそれらを取得します。

2. 依存関係

始める前に、build.sbtファイルの依存関係にAkkaを追加します。

libraryDependencies += "com.typesafe.akka" %% "akka-actor-typed" % "2.6.15"
libraryDependencies += "org.slf4j" % "slf4j-simple" % "1.7.21"

3. アクターの発見可能性を使用する理由

通常、Akkaアクターは、次の5つの方法のいずれかで取得された参照を使用して他のアクターと対話します。

  • 別のアクター内にアクターを作成する
  • アクター参照をアクターコンストラクターに渡す
  • アクター名を使用してactorSelection関数からアクター参照を取得する
  • メッセージでアクター参照を送信する
  • 送信者参照を使用して、別のメッセージへの返信としてメッセージを送信する

アクターの検出可能性は、サービスキーとそれに対応するアクターが登録されている限り、アクターシステムに存在するアクターを取得するための集中型ソリューションを提供します。

4. アクターの発見可能性はどのように機能しますか?

Akkaには、 aレセプションと呼ばれる組み込みのアクターがあります。これは、アクターから登録要求を受信し、アクター参照を要求したアクターにアクター参照を送信します。 これは、ServiceKeyからアクター参照へのルックアップテーブルマッピングとして機能します。

受付係はそれ自体がアクターであるため、メッセージを使用して通信します。 受付係の応答もメッセージです。 したがって、要求側のアクターは、アクターのリスト(受付係からの応答)を受信できるように準備する必要があります。

5. 例

この例では、加算や乗算などの数学計算を実行するアクターシステムを実装します。 タイプOperations.Calculateのメッセージを受信し、計算結果をログに記録する個別のアクターとして、両方の操作を実装します。

5.1. アクターの定義

AdditionおよびMultiplicationアクターを定義しましょう。

object Addition {
  def apply(): Behavior[Operations.Calculate] = Behaviors.setup { context =>
    Behaviors.receiveMessage[Operations.Calculate] {
      message =>
        context.log.info(s"${message.a} + ${message.b} = ${message.a + message.b}")
        Behaviors.same
    }
  }
}

object Multiplication {
  def apply(): Behavior[Operations.Calculate] = Behaviors.setup { context =>
    Behaviors.receiveMessage[Operations.Calculate] {
      message =>
        context.log.info(s"${message.a} * ${message.b} = ${message.a * message.b}")
        Behaviors.same
    }
  }
}

5.2. アクターの登録

次に、これらのアクターのインスタンスを作成します。 サービスキーを定義し、アクターインスタンスを生成し、それらをReceptionistアクターに登録する追加のアクターを使用します。 アクターは自分自身を登録できるため、登録コードをAdditionおよびMultiplicationアクターに移動できることに注意してください。

アクターを作成するためのOperationsオブジェクトを定義しましょう。

object Operations {
  final case class Setup()
  final case class Calculate(a: Int, b: Int)

  val AdditionKey = ServiceKey[Operations.Calculate]("addition")
  val MultiplicationKey = ServiceKey[Operations.Calculate]("multiplication")

  def apply(): Behavior[Setup] = Behaviors.setup{ context =>
    Behaviors.receiveMessage[Setup]{ _ =>
      context.log.info("Registering operations...")

      val addition = context.spawnAnonymous(Addition())
      context.system.receptionist ! Receptionist.Register(Operations.AdditionKey, addition)
      context.log.info("Registered addition")

      val multiplication = context.spawnAnonymous(Multiplication())
      context.system.receptionist ! Receptionist.Register(Operations.MultiplicationKey, multiplication)
      context.log.info("Registered multiplication")

      Behaviors.same
    }
  }
}

上記のコードでは、最初に、アクター間で渡されるメッセージとして使用する2つのケースクラスを定義しました。 次に、アクターサービスキーを定義します。 後で、サービスキーを使用して、受付係からアクター参照を取得します。

アクター本体では、 Setup リクエストを取得するまで待機してから、アクターインスタンスを生成し、Receptionistに登録します。

5.3. アクターの取得と使用

これで、 Calculator アクターを実装する準備が整いました。これは、前に定義したアクターを使用します。

object Calculator {
  sealed trait Command
  final case class Calculate(operation: String, a: Int, b: Int) extends Command
  final case class CalculateUsingOperation(operation: ActorRef[Operations.Calculate], a: Int, b: Int) extends Command

  ...

}

最初のステップでは、アクターによって取得されたメッセージの階層を作成する必要があります。 アクターは、操作名が付いた Calculate メッセージを受信し、受付係にアクター参照を要求し、それを自分自身に送信されるメッセージとして受信します。 したがって、2番目のメッセージには、操作名の代わりにアクター参照が含まれています。

コードを単純化するために、操作名をServiceKeyにマッピングするヘルパー関数を実装します。

private def getKey = (operationName: String) => {
  operationName match {
    case "addition" => Operations.AdditionKey
    case "multiplication" => Operations.MultiplicationKey
  }
}

次に、SetupリクエストをOperationsアクターに送信するロジックを実装します。 これにより、追加乗算が生成され、受付係に登録され、アクター参照を要求して計算リクエストを処理し、計算を要求するアクター参照。

この実装をapply関数に追加しましょう。

def apply(): Behavior[Command] = Behaviors.setup{ context =>
  context.spawn(Operations(), "operations") ! Operations.Setup()

  implicit val timeout: Timeout = Timeout.apply(100, TimeUnit.MILLISECONDS)

  Behaviors.receiveMessagePartial[Command] {
    case Calculate(operation, a, b) => {
      context.log.info(s"Looking for implementation of ${operation}")
      val operationKey = getKey(operation)
      context.ask(
        context.system.receptionist,
        Receptionist.Find(operationKey)
      ) {
           case Success(listing) => {
             val instances = listing.serviceInstances(operationKey)
             val firstImplementation = instances.iterator.next()
             CalculateUsingOperation(firstImplementation, a, b)
           }
        }

       Behaviors.same
    }

    case CalculateUsingOperation(operation, a, b) => {
      context.log.info("Calculating...")
      operation ! Operations.Calculate(a, b)
      Behaviors.same
    }
  }
}

5.4. アクターシステムの起動

これで、アクターシステムを開始し、2つの計算を要求できます。

object MathActorDiscovery extends App {
  val system: ActorSystem[Calculator.Calculate] = ActorSystem(Calculator(), "calculator")

  system ! Calculator.Calculate("addition", 3, 5)
  system ! Calculator.Calculate("multiplication", 3, 5)

  system.terminate()
}

6. 俳優登録の購読

受付係にアクター参照を要求することに加えて、アクターが指定されたサービスキーに登録するたびにメッセージを受け取ることもできます。 この機能を使用するには、Receiveist.SubscribeメッセージをReceptionistに送信する必要があります。

ServiceKey の両方にサブスクライブし、アクターパスをログに記録するRegistrationListenerアクターを実装しましょう。

object RegistrationListener {
  def apply(): Behavior[Receptionist.Listing] = Behaviors.setup {
    context =>
      context.system.receptionist ! Receptionist.Subscribe(Operations.AdditionKey, context.self)
      context.system.receptionist ! Receptionist.Subscribe(Operations.MultiplicationKey, context.self)

      Behaviors.receiveMessage[Receptionist.Listing] {
        listing =>
        val key = listing.key
        listing.getServiceInstances(key).forEach{ reference =>
        context.log.info(s"Registered: ${reference.path}")
      }
      Behaviors.same
    }
  }
}

Calculatorセットアップでアクターをスポーンすることを忘れないでください。

context.spawnAnonymous(RegistrationListener())

2つの追加のログメッセージが表示されます。

[calculator-akka.actor.default-dispatcher-7] INFO com.baeldung.akka.RegistrationListener$ - Registered: akka://calculator/user/operations/$a
[calculator-akka.actor.default-dispatcher-7] INFO com.baeldung.akka.RegistrationListener$ - Registered: akka://calculator/user/operations/$b

7. アクターの登録解除

アクターが不要になったら、アクターシステムからアクターインスタンスを削除する前に、アクターの登録を解除する必要があります。

context.system.receptionist ! Receptionist.Deregister(Operations.AdditionKey, context.self)

Receptionist はアクターの登録をすぐに解除しないため、しばらくの間、登録解除されたアクターのアクター参照を返す可能性があることに注意してください。

8. 結論

この記事では、Akkaのアクター検出機能の使用方法、アクター参照の登録、登録のサブスクライブ、 ServiceKey によるアクターの取得、およびアクターの登録解除の方法を学習しました。

いつものように、私たちのアプリケーションのソースコードはGitHubから入手できます。