我们的第一个查询协议是正确的,但没有考虑分布式应用程序执行。如果我们想在查询设备Actor中实现重发(由于超时请求),或者如果我们想查询多个Actor,我们需要能够关联请求和响应。因此,我们在消息中再添加一个字段,以便请求者可以提供ID(我们将在稍后的步骤中将此代码添加到我们的应用程序中):
定义设备actor及其读取协议
正如我们在Hello World示例中所了解到的,每个actor都定义了它将接受的消息类型。我们的设备Actor有责任对给定查询的响应使用相同的ID参数,这将使其看起来如下所示。
import java.util.Optional; import akka.actor.AbstractActor; import akka.actor.Props; import akka.event.Logging; import akka.event.LoggingAdapter; class Device extends AbstractActor { private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); final String groupId; final String deviceId; public Device(String groupId, String deviceId) { this.groupId = groupId; this.deviceId = deviceId; } public static Props props(String groupId, String deviceId) { return Props.create(Device.class, groupId, deviceId); } public static final class ReadTemperature { long requestId; public ReadTemperature(long requestId) { this.requestId = requestId; } } public static final class RespondTemperature { long requestId; Optional<Double> value; public RespondTemperature(long requestId, Optional<Double> value) { this.requestId = requestId; this.value = value; } } Optional<Double> lastTemperatureReading = Optional.empty(); @Override public void preStart() { log.info("Device actor {}-{} started", groupId, deviceId); } @Override public void postStop() { log.info("Device actor {}-{} stopped", groupId, deviceId); } @Override public Receive createReceive() { return receiveBuilder() .match(ReadTemperature.class, r -> { getSender().tell(new RespondTemperature(r.requestId, lastTemperatureReading), getSelf()); }) .build(); } }
请注意代码中:
- static方法定义了如何构造Device actor,props参数包括设备的ID和它所属的组,稍后我们将使用它们。
- 该类包括我们之前推论的消息的定义。
- 在Device类中,lastTemperatureReading的值最初设置为Optional.empty(),并且actor将在查询时将其报告回来。
测试Actor
基于上面的简单Actor,我们可以编写一个简单的测试。在项目的测试目录中中的com.lightbend.akka.sample包中,将以下代码添加到DeviceTest.java文件中。
您可以通过运行mvn test或在sbt提示符下运行test来运行此测试。
@Test public void testReplyWithEmptyReadingIfNoTemperatureIsKnown() { TestKit probe = new TestKit(system); ActorRef deviceActor = system.actorOf(Device.props("group", "device")); deviceActor.tell(new Device.ReadTemperature(42L), probe.getRef()); Device.RespondTemperature response = probe.expectMsgClass(Device.RespondTemperature.class); assertEquals(42L, response.requestId); assertEquals(Optional.empty(), response.value); }
现在,Actor需要一种方法来从传感器接收到消息时改变温度状态。
添加写协议
写入协议的目的是在actor接收包含温度的消息时更新currentTemperature字段。同样,很容易将写协议定义为一个非常简单的消息,如下所示:
public static final class RecordTemperature { final double value; public RecordTemperature(double value) { this.value = value; } }
但是,这种方法没有考虑到记录温度消息的发送者永远无法确定消息是否被处理。我们已经看到Akka不保证传递这些消息并将其留给应用程序以提供成功通知。在这种情况下,我们希望在更新上次温度记录后向发件人发送确认,例如TemperatureRecorded。就像温度查询和响应一样,最好包含一个ID字段以提供最大的灵活性。
读取和写入消息的Actor
将读写协议放在一起,设备Actor看起来像下面的例子:
import java.util.Optional; import akka.actor.AbstractActor; import akka.actor.AbstractActor.Receive; import akka.actor.Props; import akka.event.Logging; import akka.event.LoggingAdapter; public class Device extends AbstractActor { private final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this); final String groupId; final String deviceId; public Device(String groupId, String deviceId) { this.groupId = groupId; this.deviceId = deviceId; } public static Props props(String groupId, String deviceId) { return Props.create(Device.class, groupId, deviceId); } public static final class RecordTemperature { final long requestId; final double value; public RecordTemperature(long requestId, double value) { this.requestId = requestId; this.value = value; } } public static final class TemperatureRecorded { final long requestId; public TemperatureRecorded(long requestId) { this.requestId = requestId; } } public static final class ReadTemperature { final long requestId; public ReadTemperature(long requestId) { this.requestId = requestId; } } public static final class RespondTemperature { final long requestId; final Optional<Double> value; public RespondTemperature(long requestId, Optional<Double> value) { this.requestId = requestId; this.value = value; } } Optional<Double> lastTemperatureReading = Optional.empty(); @Override public void preStart() { log.info("Device actor {}-{} started", groupId, deviceId); } @Override public void postStop() { log.info("Device actor {}-{} stopped", groupId, deviceId); } @Override public Receive createReceive() { return receiveBuilder() .match(RecordTemperature.class, r -> { log.info("Recorded temperature reading {} with {}", r.value, r.requestId); lastTemperatureReading = Optional.of(r.value); getSender().tell(new TemperatureRecorded(r.requestId), getSelf()); }) .match(ReadTemperature.class, r -> { getSender().tell(new RespondTemperature(r.requestId, lastTemperatureReading), getSelf()); }) .build(); } }
我们现在还应该编写一个新的测试用例,同时执行读/查和写/记录功能:
@Test public void testReplyWithLatestTemperatureReading() { TestKit probe = new TestKit(system); ActorRef deviceActor = system.actorOf(Device.props("group", "device")); deviceActor.tell(new Device.RecordTemperature(1L, 24.0), probe.getRef()); assertEquals(1L, probe.expectMsgClass(Device.TemperatureRecorded.class).requestId); deviceActor.tell(new Device.ReadTemperature(2L), probe.getRef()); Device.RespondTemperature response1 = probe.expectMsgClass(Device.RespondTemperature.class); assertEquals(2L, response1.requestId); assertEquals(Optional.of(24.0), response1.value); deviceActor.tell(new Device.RecordTemperature(3L, 55.0), probe.getRef()); assertEquals(3L, probe.expectMsgClass(Device.TemperatureRecorded.class).requestId); deviceActor.tell(new Device.ReadTemperature(4L), probe.getRef()); Device.RespondTemperature response2 = probe.expectMsgClass(Device.RespondTemperature.class); assertEquals(4L, response2.requestId); assertEquals(Optional.of(55.0), response2.value); }
What’s Next?
到目前为止,我们已经开始设计我们的整体架构,并且我们编写了第一个直接对应领域的actor。我们现在必须创建负责维护设备组和设备Actor本身的组件。
下节继续!
原文:https://doc.akka.io/docs/akka/2.5/guide/tutorial_3.html
有什么讨论的内容,可以加我公众号: