Scala 如何将基于Actor的逻辑迁移到Akka流?
我已经使用Akka应用程序一段时间了。95%的代码是用纯参与者编写的。现在我将把应用程序的一些部分移动到Akka Streams。 请告诉我以下逻辑在Akka流中的表现:Scala 如何将基于Actor的逻辑迁移到Akka流?,scala,akka,akka-stream,Scala,Akka,Akka Stream,我已经使用Akka应用程序一段时间了。95%的代码是用纯参与者编写的。现在我将把应用程序的一些部分移动到Akka Streams。 请告诉我以下逻辑在Akka流中的表现: +------------+ | CreateUser | +------------+
+------------+
| CreateUser |
+------------+
|
|
+------------+ +-------------------+
| CheckEmail |-----|EmailIsAlreadyInUse|
+------------+ +-------------------+
|
|
+------------+ +-------------------+
|3rdPartyCall|-----|NoUserInInternalDB |
+------------+ +-------------------+
|
|
+------------+ +-------------------+
| SaveUser |-----| UserDBError |
+------------+ +-------------------+
|
|
+------------+
| UserSaved |
+------------+
在当前的实现中,所有的块都是我发送给适当的参与者的消息。如果消息流成功,我将向发送者发回一条UserSaved
消息。否则,我会将其中一条验证消息发回发件人:EmailIsAlreadyInUse
或nouserinenternaldb
或UserDBError
以下是一组消息:
case class CreateUser(email: String)
case class CheckEmailUniqueness(email: String)
case class ExternalServiceValidation(email: String)
case class SaveUser(email: String)
sealed trait CreateUserResult
sealed trait CreateUserError
case class UserCreated(email: String) extends CreateUserResult
case class EmailIsAlreadyInUse(email: String) extends CreateUserResult with CreateUserError
case class NoUserInExternalDB(email: String) extends CreateUserResult with CreateUserError
case class UserDBError(email: String) extends CreateUserResult with CreateUserError
如何将此逻辑迁移到Akka Streams?消息结构 因为akka流数据从源到接收器以一个方向发送消息,所以没有“发送回发送方”功能。您唯一的选择是不断地将消息转发到下一步 因此,我认为您只需要在消息周围添加一些额外的结构。该构造似乎对此很有用。假设您的
CreateUser
Actor有一个独立的函数:
def createUserFunction(createUser : CreateUser) : UserCreated = ???
然后,可以使用一个函数来检查电子邮件:
val Set[String] existingEmails = ???
def checkEmailUniqueness(userCreated : UserCreated) : Either[CreateUserError, UserCreated] =
if(existingEmails contains userCreated.email)
Left(EmailIsAlreadyInUse(userCreated.email))
else
Right(createUser)
类似地,3rdPartyCall
也将返回一个:
def thirdPartyLibraryFunction(userCreated : UserCreated) : Boolean = ???
def thirdPartyCall(userCreated : UserCreated) : Either[CreateUserError, UserCreated] =
if(!thirdPartyLibraryFunction(userCreated))
Left(NoUserInExternalDB(userCreated.email))
else
Right(userCreated)
阿克卡河建设
通过这种结构化的消息传递,您现在可以创建一个只向一个方向移动的流。我们首先创建一个流
,用于执行用户创建:
val createUserFlow : Flow[CreateUser, UserCreated, _] =
Flow[CreateUser] map (createUserFunction)
然后是电子邮件检查流:
val emailFlow : Flow[UserCreated, Either[CreateUserError, UserCreated],_] =
Flow[UserCreated] map (checkEmailUniqueness)
现在生成第三方调用的流:
val thirdPartyFlow : Flow[UserCreated, Either[CreateUserError, UserCreated],_] =
Flow[UserCreated] map (_ flatMap thirdPartyCall)
这些流现在可以形成一个流的基础,连同<代码>源< /代码>和<代码>接收器< /代码>:
val userSource : Source[CreateUser, _] = ???
val userSink : Sink[Either[CreateUserError, UserCreated], _] =
Sink[Either[CreateUserError, UserCreated]] foreach {
case Left(error) =>
System.err.println("Error with user creation : " error.email)
case Right(userCreated) =>
System.out.println("User Created: " userCreated.email)
}
//create the full stream
userSource
.via(createUserFlow)
.via(emailFlow)
.via(thirdPartyFlow)
.to(userSink)
.run()
消息结构 因为akka流数据从源到接收器以一个方向发送消息,所以没有“发送回发送方”功能。您唯一的选择是不断地将消息转发到下一步 因此,我认为您只需要在消息周围添加一些额外的结构。该构造似乎对此很有用。假设您的
CreateUser
Actor有一个独立的函数:
def createUserFunction(createUser : CreateUser) : UserCreated = ???
然后,可以使用一个函数来检查电子邮件:
val Set[String] existingEmails = ???
def checkEmailUniqueness(userCreated : UserCreated) : Either[CreateUserError, UserCreated] =
if(existingEmails contains userCreated.email)
Left(EmailIsAlreadyInUse(userCreated.email))
else
Right(createUser)
类似地,3rdPartyCall
也将返回一个:
def thirdPartyLibraryFunction(userCreated : UserCreated) : Boolean = ???
def thirdPartyCall(userCreated : UserCreated) : Either[CreateUserError, UserCreated] =
if(!thirdPartyLibraryFunction(userCreated))
Left(NoUserInExternalDB(userCreated.email))
else
Right(userCreated)
阿克卡河建设
通过这种结构化的消息传递,您现在可以创建一个只向一个方向移动的流。我们首先创建一个流
,用于执行用户创建:
val createUserFlow : Flow[CreateUser, UserCreated, _] =
Flow[CreateUser] map (createUserFunction)
然后是电子邮件检查流:
val emailFlow : Flow[UserCreated, Either[CreateUserError, UserCreated],_] =
Flow[UserCreated] map (checkEmailUniqueness)
现在生成第三方调用的流:
val thirdPartyFlow : Flow[UserCreated, Either[CreateUserError, UserCreated],_] =
Flow[UserCreated] map (_ flatMap thirdPartyCall)
这些流现在可以形成一个流的基础,连同<代码>源< /代码>和<代码>接收器< /代码>:
val userSource : Source[CreateUser, _] = ???
val userSink : Sink[Either[CreateUserError, UserCreated], _] =
Sink[Either[CreateUserError, UserCreated]] foreach {
case Left(error) =>
System.err.println("Error with user creation : " error.email)
case Right(userCreated) =>
System.out.println("User Created: " userCreated.email)
}
//create the full stream
userSource
.via(createUserFlow)
.via(emailFlow)
.via(thirdPartyFlow)
.to(userSink)
.run()
雷蒙:谢谢你这么详细的解释!总的来说,这个想法很清楚。我只有一句不重要的话:首先,我们需要检查电子邮件,然后进行第三方呼叫,如果没有错误,我们才进行第三方呼叫。Ramon感谢您的详细解释!总的来说,这个想法很清楚。我只有一句不重要的话:首先,我们需要检查电子邮件,然后进行第三方呼叫,只有在没有错误的情况下,我们才可以进行第三方呼叫。
SaveUser
:)