Scala 如何将基于Actor的逻辑迁移到Akka流?

Scala 如何将基于Actor的逻辑迁移到Akka流?,scala,akka,akka-stream,Scala,Akka,Akka Stream,我已经使用Akka应用程序一段时间了。95%的代码是用纯参与者编写的。现在我将把应用程序的一些部分移动到Akka Streams。 请告诉我以下逻辑在Akka流中的表现: +------------+ | CreateUser | +------------+

我已经使用Akka应用程序一段时间了。95%的代码是用纯参与者编写的。现在我将把应用程序的一些部分移动到Akka Streams。 请告诉我以下逻辑在Akka流中的表现:

+------------+                                             
| 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
:)