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

时间:2018-04-08 11:12:37

标签: scala akka akka-stream

我一直在使用Akka应用程序。 95%的代码是用纯粹的演员编写的。现在我要将应用程序的某些部分移动到Akka Streams。 让我了解以下逻辑如何看待Akka Streams:

+------------+                                             
| CreateUser |                                             
+------------+                                             
      |                                                    
      |                                                    
+------------+     +-------------------+                   
| CheckEmail |-----|EmailIsAlreadyInUse|                   
+------------+     +-------------------+                   
      |                                                    
      |                                                    
+------------+     +-------------------+                   
|3rdPartyCall|-----|NoUserInInternalDB |                   
+------------+     +-------------------+                   
      |                                                    
      |                                                    
+------------+     +-------------------+                   
|  SaveUser  |-----|    UserDBError    |                   
+------------+     +-------------------+                   
      |                                                    
      |                                                    
+------------+                                             
| UserSaved  |                                             
+------------+   

在当前实现中,所有块都是我发送给适当的actor的消息。如果消息流成功,我会向发件人发送UserSaved消息。否则,我会将一封验证邮件发送给发件人:EmailIsAlreadyInUseNoUserInInternalDBUserDBError

以下是一组消息:

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?

1 个答案:

答案 0 :(得分:3)

讯息结构

因为akka-stream数据是在一个方向上发送的,从源到接收器,所以没有"发送回发送者"功能。您唯一的选择是不断将消息转发到下一步。

因此,我认为您只需要在消息周围添加一些额外的结构。 Either构造似乎对此有用。让我们假设您的CreateUser Actor具有自包含功能:

def createUserFunction(createUser : CreateUser) : UserCreated = ???

然后可以跟一个函数CheckEmail

val Set[String] existingEmails = ???

def checkEmailUniqueness(userCreated : UserCreated) : Either[CreateUserError, UserCreated] =
  if(existingEmails contains userCreated.email)
    Left(EmailIsAlreadyInUse(userCreated.email))
  else
    Right(createUser)

同样地,3rdPartyCall woul也会返回一个Either:

 def thirdPartyLibraryFunction(userCreated : UserCreated) : Boolean = ???

 def thirdPartyCall(userCreated : UserCreated) : Either[CreateUserError, UserCreated] = 
   if(!thirdPartyLibraryFunction(userCreated))
     Left(NoUserInExternalDB(userCreated.email))
   else
     Right(userCreated)

Akka Stream Construction

使用此结构化消息传递,您现在可以创建仅在单个方向上移动的流。我们首先制作一个用于创建用户的Flow

 val createUserFlow : Flow[CreateUser, UserCreated, _] = 
   Flow[CreateUser] map (createUserFunction)

然后通过电子邮件检查流程:

 val emailFlow : Flow[UserCreated, Either[CreateUserError, UserCreated],_] = 
   Flow[UserCreated] map (checkEmailUniqueness)

现在让Flow进行第三方通话:

 val thirdPartyFlow : Flow[UserCreated, Either[CreateUserError, UserCreated],_] = 
   Flow[UserCreated] map (_ flatMap thirdPartyCall)

这些流现在可以构成流的基础以及SourceSink

 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()